Unity 8
 All Classes Functions
Shell.qml
1 /*
2  * Copyright (C) 2013 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.0
18 import AccountsService 0.1
19 import GSettings 1.0
20 import Unity.Application 0.1
21 import Ubuntu.Components 0.1
22 import Ubuntu.Components.Popups 1.0
23 import Ubuntu.Gestures 0.1
24 import Ubuntu.SystemImage 0.1
25 import Ubuntu.Telephony 0.1 as Telephony
26 import Unity.Connectivity 0.1
27 import Unity.Launcher 0.1
28 import Utils 0.1
29 import LightDM 0.1 as LightDM
30 import Powerd 0.1
31 import SessionBroadcast 0.1
32 import "Greeter"
33 import "Launcher"
34 import "Panel"
35 import "Components"
36 import "Notifications"
37 import "Stages"
38 import Unity.Notifications 1.0 as NotificationBackend
39 import Unity.Session 0.1
40 import Unity.DashCommunicator 0.1
41 
42 Item {
43  id: shell
44 
45  // this is only here to select the width / height of the window if not running fullscreen
46  property bool tablet: false
47  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
48  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
49 
50  property real edgeSize: units.gu(2)
51  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
52  property url background
53  readonly property real panelHeight: panel.panelHeight
54 
55  readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
56 
57  property bool sideStageEnabled: shell.width >= units.gu(100)
58  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
59 
60  property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
61  property int failedLoginsDelayAttempts: 7 // number of failed logins
62  property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
63 
64  function activateApplication(appId) {
65  if (ApplicationManager.findApplication(appId)) {
66  ApplicationManager.requestFocusApplication(appId);
67  } else {
68  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
69  ApplicationManager.startApplication(appId, execFlags);
70  }
71  }
72 
73  function setFakeActiveForApp(app) {
74  if (shell.locked) {
75  greeter.fakeActiveForApp = app
76  lockscreen.hide()
77  }
78  }
79 
80  Binding {
81  target: LauncherModel
82  property: "applicationManager"
83  value: ApplicationManager
84  }
85 
86  Component.onCompleted: {
87  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
88  }
89 
90  GSettings {
91  id: backgroundSettings
92  schema.id: "org.gnome.desktop.background"
93  }
94  property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
95  onGSettingsPictureChanged: {
96  shell.background = gSettingsPicture
97  }
98 
99  VolumeControl {
100  id: volumeControl
101  }
102 
103  DashCommunicator {
104  id: dash
105  objectName: "dashCommunicator"
106  }
107 
108  WindowKeysFilter {
109  // Handle but do not filter out volume keys
110  Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
111  Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
112 
113  Keys.onPressed: {
114  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
115  dialogs.onPowerKeyPressed();
116  event.accepted = true;
117  } else {
118  event.accepted = false;
119  }
120  }
121 
122  Keys.onReleased: {
123  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
124  dialogs.onPowerKeyReleased();
125  event.accepted = true;
126  } else {
127  event.accepted = false;
128  }
129  }
130  }
131 
132  Item {
133  id: stages
134  objectName: "stages"
135  width: parent.width
136  height: parent.height
137  visible: !ApplicationManager.empty
138 
139  Connections {
140  target: ApplicationManager
141  onFocusRequested: {
142  if (appId === "dialer-app") {
143  // Always let the dialer-app through. Either user asked
144  // for an emergency call or accepted an incoming call.
145  setFakeActiveForApp(appId)
146  } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
147  lockscreen.show();
148  }
149  greeter.hide();
150  launcher.hide();
151  }
152 
153  onFocusedApplicationIdChanged: {
154  if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
155  lockscreen.show();
156  }
157  panel.indicators.hide();
158  }
159 
160  onApplicationAdded: {
161  if (greeter.shown && appId != "unity8-dash") {
162  greeter.hide();
163  }
164  if (appId === "dialer-app") {
165  // Always let the dialer-app through. Either user asked
166  // for an emergency call or accepted an incoming call.
167  setFakeActiveForApp(appId)
168  }
169  launcher.hide();
170  }
171  }
172 
173  Loader {
174  id: applicationsDisplayLoader
175  anchors.fill: parent
176 
177  source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
178 
179  Binding {
180  target: applicationsDisplayLoader.item
181  property: "objectName"
182  value: "stage"
183  }
184  Binding {
185  target: applicationsDisplayLoader.item
186  property: "dragAreaWidth"
187  value: shell.edgeSize
188  }
189  Binding {
190  target: applicationsDisplayLoader.item
191  property: "maximizedAppTopMargin"
192  // Not just using panel.panelHeight as that changes depending on the focused app.
193  value: panel.indicators.panelHeight
194  }
195  Binding {
196  target: applicationsDisplayLoader.item
197  property: "interactive"
198  value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
199  }
200  Binding {
201  target: applicationsDisplayLoader.item
202  property: "spreadEnabled"
203  value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
204  }
205  Binding {
206  target: applicationsDisplayLoader.item
207  property: "inverseProgress"
208  value: launcher.progress
209  }
210  }
211  }
212 
213  InputMethod {
214  id: inputMethod
215  objectName: "inputMethod"
216  anchors { fill: parent; topMargin: panel.panelHeight }
217  z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
218  }
219 
220  Connections {
221  target: SurfaceManager
222  onSurfaceCreated: {
223  if (surface.type == MirSurfaceItem.InputMethod) {
224  inputMethod.surface = surface;
225  }
226  }
227 
228  onSurfaceDestroyed: {
229  if (inputMethod.surface == surface) {
230  inputMethod.surface = null;
231  surface.parent = null;
232  }
233  if (!surface.parent) {
234  // there's no one displaying it. delete it right away
235  surface.release();
236  }
237  }
238  }
239  Connections {
240  target: SessionManager
241  onSessionStopping: {
242  if (!session.parentSession && !session.application) {
243  // nothing is using it. delete it right away
244  session.release();
245  }
246  }
247  }
248 
249  Lockscreen {
250  id: lockscreen
251  objectName: "lockscreen"
252 
253  readonly property int backgroundTopMargin: -panel.panelHeight
254 
255  hides: [launcher, panel.indicators]
256  shown: false
257  enabled: true
258  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
259  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
260  y: panel.panelHeight
261  visible: required
262  width: parent.width
263  height: parent.height - panel.panelHeight
264  background: shell.background
265  alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
266  minPinLength: 4
267  maxPinLength: 4
268 
269  onEntered: LightDM.Greeter.respond(passphrase);
270  onCancel: greeter.show()
271  onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
272 
273  onShownChanged: if (shown) greeter.fakeActiveForApp = ""
274 
275  Component.onCompleted: {
276  if (greeter.narrowMode) {
277  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
278  }
279  }
280  }
281 
282  Connections {
283  target: LightDM.Greeter
284 
285  onShowGreeter: greeter.show()
286  onHideGreeter: greeter.login()
287 
288  onShowPrompt: {
289  if (greeter.narrowMode) {
290  if (isDefaultPrompt) {
291  if (lockscreen.alphaNumeric) {
292  lockscreen.infoText = i18n.tr("Enter your passphrase")
293  lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
294  } else {
295  lockscreen.infoText = i18n.tr("Enter your passcode")
296  lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
297  }
298  } else {
299  lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
300  lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
301  }
302 
303  lockscreen.show();
304  }
305  }
306 
307  onPromptlessChanged: {
308  if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
309  lockscreen.hide()
310  } else {
311  lockscreen.reset();
312  lockscreen.show();
313  }
314  }
315 
316  onAuthenticationComplete: {
317  if (LightDM.Greeter.authenticated) {
318  AccountsService.failedLogins = 0
319  }
320  // Else only penalize user for a failed login if they actually were
321  // prompted for a password. We do this below after the promptless
322  // early exit.
323 
324  if (LightDM.Greeter.promptless) {
325  return;
326  }
327 
328  if (LightDM.Greeter.authenticated) {
329  greeter.login();
330  } else {
331  AccountsService.failedLogins++
332  if (maxFailedLogins >= 2) { // require at least a warning
333  if (AccountsService.failedLogins === maxFailedLogins - 1) {
334  var title = lockscreen.alphaNumeric ?
335  i18n.tr("Sorry, incorrect passphrase.") :
336  i18n.tr("Sorry, incorrect passcode.")
337  var text = i18n.tr("This will be your last attempt.") + " " +
338  (lockscreen.alphaNumeric ?
339  i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
340  i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
341  lockscreen.showInfoPopup(title, text)
342  } else if (AccountsService.failedLogins >= maxFailedLogins) {
343  SystemImage.factoryReset() // Ouch!
344  }
345  }
346  if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
347  lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
348  }
349 
350  lockscreen.clear(true);
351  if (greeter.narrowMode) {
352  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
353  }
354  }
355  }
356  }
357 
358  Binding {
359  target: LightDM.Greeter
360  property: "active"
361  value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
362  }
363 
364  Rectangle {
365  anchors.fill: parent
366  color: "black"
367  opacity: greeterWrapper.showProgress * 0.8
368  }
369 
370  Item {
371  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
372  id: greeterWrapper
373  x: launcher.progress
374  y: panel.panelHeight
375  width: parent.width
376  height: parent.height - panel.panelHeight
377 
378  Behavior on x {
379  enabled: !launcher.dashSwipe
380  StandardAnimation {}
381  }
382 
383  property bool fullyShown: showProgress === 1.0
384  onFullyShownChanged: {
385  // Wait until the greeter is completely covering lockscreen before resetting it.
386  if (fullyShown && !LightDM.Greeter.authenticated) {
387  lockscreen.reset();
388  lockscreen.show();
389  }
390  }
391 
392  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
393  onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
394 
395  Greeter {
396  id: greeter
397  objectName: "greeter"
398 
399  signal sessionStarted() // helpful for tests
400 
401  property string fakeActiveForApp: ""
402 
403  available: true
404  hides: [launcher, panel.indicators]
405  shown: true
406  loadContent: required || lockscreen.required // keeps content in memory for quick show()
407 
408  defaultBackground: shell.background
409 
410  width: parent.width
411  height: parent.height
412 
413  dragHandleWidth: shell.edgeSize
414 
415  function login() {
416  enabled = false;
417  if (LightDM.Greeter.startSessionSync()) {
418  sessionStarted();
419  greeter.hide();
420  lockscreen.hide();
421  launcher.hide();
422  }
423  enabled = true;
424  }
425 
426  onShownChanged: {
427  if (shown) {
428  if (greeter.narrowMode) {
429  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
430  }
431  greeter.fakeActiveForApp = "";
432  greeter.forceActiveFocus();
433  }
434  }
435 
436  /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
437  Component.onCompleted: {
438  Connectivity.unlockAllModems()
439  } */
440 
441  onUnlocked: greeter.hide()
442  onSelected: {
443  // Update launcher items for new user
444  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
445  AccountsService.user = user;
446  LauncherModel.setUser(user);
447  }
448 
449  onTease: launcher.tease()
450 
451  Binding {
452  target: ApplicationManager
453  property: "suspended"
454  value: greeter.shown && greeterWrapper.showProgress == 1
455  }
456  }
457  }
458 
459  Connections {
460  id: powerConnection
461  target: Powerd
462 
463  onStatusChanged: {
464  if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
465  greeter.showNow()
466  }
467  }
468  }
469 
470  function showHome() {
471  if (edgeDemo.running) {
472  return
473  }
474 
475  if (LightDM.Greeter.active) {
476  if (!LightDM.Greeter.authenticated) {
477  lockscreen.show()
478  }
479  greeter.hide()
480  }
481 
482  var animate = !LightDM.Greeter.active && !stages.shown
483  dash.setCurrentScope("clickscope", animate, false)
484  ApplicationManager.requestFocusApplication("unity8-dash")
485  }
486 
487  function showDash() {
488  if (shell.locked) {
489  return;
490  }
491  if (greeter.shown) {
492  greeter.hideRight();
493  launcher.fadeOut();
494  }
495 
496  ApplicationManager.requestFocusApplication("unity8-dash")
497  launcher.fadeOut();
498  }
499 
500  Item {
501  id: overlay
502  z: 10
503 
504  anchors.fill: parent
505 
506  Panel {
507  id: panel
508  objectName: "panel"
509  anchors.fill: parent //because this draws indicator menus
510  indicators {
511  hides: [launcher]
512  available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
513  contentEnabled: edgeDemo.panelContentEnabled
514  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
515  panelHeight: units.gu(3)
516  }
517 
518  property bool topmostApplicationIsFullscreen:
519  ApplicationManager.focusedApplicationId &&
520  ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
521 
522  fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
523  || greeter.fakeActiveForApp !== ""
524  }
525 
526  Launcher {
527  id: launcher
528  objectName: "launcher"
529 
530  readonly property bool dashSwipe: progress > 0
531 
532  anchors.top: parent.top
533  anchors.bottom: parent.bottom
534  width: parent.width
535  dragAreaWidth: shell.edgeSize
536  available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
537 
538  onShowDashHome: showHome()
539  onDash: showDash()
540  onDashSwipeChanged: {
541  if (dashSwipe) {
542  dash.setCurrentScope("clickscope", false, true)
543  }
544  }
545  onLauncherApplicationSelected: {
546  if (greeter.fakeActiveForApp !== "") {
547  lockscreen.show()
548  }
549  if (!edgeDemo.running)
550  shell.activateApplication(appId)
551  }
552  onShownChanged: {
553  if (shown) {
554  panel.indicators.hide()
555  }
556  }
557  }
558 
559  Rectangle {
560  id: modalNotificationBackground
561 
562  visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
563  color: "#000000"
564  anchors.fill: parent
565  opacity: 0.9
566 
567  MouseArea {
568  anchors.fill: parent
569  }
570  }
571 
572  Notifications {
573  id: notifications
574 
575  model: NotificationBackend.Model
576  margin: units.gu(1)
577 
578  y: panel.panelHeight
579  width: parent.width
580  height: parent.height - panel.panelHeight
581 
582  states: [
583  State {
584  name: "narrow"
585  when: overlay.width <= units.gu(60)
586  AnchorChanges { target: notifications; anchors.left: parent.left }
587  },
588  State {
589  name: "wide"
590  when: overlay.width > units.gu(60)
591  AnchorChanges { target: notifications; anchors.left: undefined }
592  PropertyChanges { target: notifications; width: units.gu(38) }
593  }
594  ]
595  }
596  }
597 
598  Dialogs {
599  id: dialogs
600  anchors.fill: parent
601  z: overlay.z + 10
602  onPowerOffClicked: {
603  shutdownFadeOutRectangle.enabled = true;
604  shutdownFadeOutRectangle.visible = true;
605  shutdownFadeOut.start();
606  }
607  }
608 
609  Label {
610  id: alphaDisclaimerLabel
611  anchors.centerIn: parent
612  visible: ApplicationManager.fake ? ApplicationManager.fake : false
613  z: dialogs.z + 10
614  text: "EARLY ALPHA\nNOT READY FOR USE"
615  color: "lightgrey"
616  opacity: 0.2
617  font.weight: Font.Black
618  horizontalAlignment: Text.AlignHCenter
619  verticalAlignment: Text.AlignVCenter
620  fontSizeMode: Text.Fit
621  rotation: -45
622  scale: Math.min(parent.width, parent.height) / width
623  }
624 
625  EdgeDemo {
626  id: edgeDemo
627  z: alphaDisclaimerLabel.z + 10
628  paused: Powerd.status === Powerd.Off // Saves power
629  greeter: greeter
630  launcher: launcher
631  indicators: panel.indicators
632  stages: stages
633  }
634 
635  Connections {
636  target: SessionBroadcast
637  onShowHome: showHome()
638  }
639 
640  Rectangle {
641  id: shutdownFadeOutRectangle
642  z: edgeDemo.z + 10
643  enabled: false
644  visible: false
645  color: "black"
646  anchors.fill: parent
647  opacity: 0.0
648  NumberAnimation on opacity {
649  id: shutdownFadeOut
650  from: 0.0
651  to: 1.0
652  onStopped: {
653  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
654  DBusUnitySessionService.Shutdown();
655  }
656  }
657  }
658  }
659 
660 }