Unity 8
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 QtQuick.Window 2.0
19 import AccountsService 0.1
20 import GSettings 1.0
21 import Unity.Application 0.1
22 import Ubuntu.Components 0.1
23 import Ubuntu.Components.Popups 1.0
24 import Ubuntu.Gestures 0.1
25 import Ubuntu.SystemImage 0.1
26 import Ubuntu.Telephony 0.1 as Telephony
27 import Unity.Connectivity 0.1
28 import Unity.Launcher 0.1
29 import Utils 0.1
30 import LightDM 0.1 as LightDM
31 import Powerd 0.1
32 import SessionBroadcast 0.1
33 import "Greeter"
34 import "Launcher"
35 import "Panel"
36 import "Components"
37 import "Notifications"
38 import "Stages"
39 import "Tutorial"
40 import "Wizard"
41 import Unity.Notifications 1.0 as NotificationBackend
42 import Unity.Session 0.1
43 import Unity.DashCommunicator 0.1
44 import Unity.Indicators 0.1 as Indicators
45 
46 Item {
47  id: shell
48 
49  // Disable everything so that user can't swipe greeter or launcher until
50  // we get first prompt/authenticate, which will re-enable the shell.
51  enabled: false
52 
53  // this is only here to select the width / height of the window if not running fullscreen
54  property bool tablet: false
55  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
56  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
57 
58  property real edgeSize: units.gu(2)
59  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
60  property url background: asImageTester.status == Image.Ready ? asImageTester.source
61  : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
62  readonly property real panelHeight: panel.panelHeight
63 
64  readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
65  readonly property alias hasLockedApp: greeter.hasLockedApp
66  readonly property bool forcedUnlock: tutorial.running
67  onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
68 
69  property bool sideStageEnabled: shell.width >= units.gu(100)
70  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
71 
72  property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
73  property int failedLoginsDelayAttempts: 7 // number of failed logins
74  property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
75 
76  property int orientation
77  readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
78  onDeviceOrientationAngleChanged: {
79  if (!OrientationLock.enabled) {
80  orientation = Screen.orientation;
81  }
82  }
83  readonly property bool orientationLockEnabled: OrientationLock.enabled
84  onOrientationLockEnabledChanged: {
85  if (orientationLockEnabled) {
86  OrientationLock.savedOrientation = Screen.orientation;
87  } else {
88  orientation = Screen.orientation;
89  }
90  }
91 
92  function activateApplication(appId) {
93  if (ApplicationManager.findApplication(appId)) {
94  ApplicationManager.requestFocusApplication(appId);
95  } else {
96  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
97  ApplicationManager.startApplication(appId, execFlags);
98  }
99  }
100 
101  function startLockedApp(app) {
102  if (shell.locked) {
103  greeter.lockedApp = app;
104  }
105  shell.activateApplication(app);
106  }
107 
108  // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
109  Image {
110  id: asImageTester
111  source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
112  height: 0
113  width: 0
114  sourceSize.height: 0
115  sourceSize.width: 0
116  }
117 
118  GSettings {
119  id: backgroundSettings
120  schema.id: "org.gnome.desktop.background"
121  }
122 
123  // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
124  Image {
125  id: gsImageTester
126  source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
127  height: 0
128  width: 0
129  sourceSize.height: 0
130  sourceSize.width: 0
131  }
132 
133  GSettings {
134  id: usageModeSettings
135  schema.id: "com.canonical.Unity8"
136  }
137 
138  Binding {
139  target: LauncherModel
140  property: "applicationManager"
141  value: ApplicationManager
142  }
143 
144  Component.onCompleted: {
145  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
146  if (ApplicationManager.count > 0) {
147  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
148  }
149  if (orientationLockEnabled) {
150  orientation = OrientationLock.savedOrientation;
151  }
152  }
153 
154  VolumeControl {
155  id: volumeControl
156  }
157 
158  DashCommunicator {
159  id: dash
160  objectName: "dashCommunicator"
161  }
162 
163  ScreenGrabber {
164  id: screenGrabber
165  z: dialogs.z + 10
166  enabled: Powerd.status === Powerd.On
167  }
168 
169  Binding {
170  target: ApplicationManager
171  property: "forceDashActive"
172  value: launcher.shown || launcher.dashSwipe
173  }
174 
175  VolumeKeyFilter {
176  id: volumeKeyFilter
177  onVolumeDownPressed: volumeControl.volumeDown()
178  onVolumeUpPressed: volumeControl.volumeUp()
179  onBothVolumeKeysPressed: screenGrabber.capture()
180  }
181 
182  WindowKeysFilter {
183  Keys.onPressed: {
184  // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
185  if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
186  event.accepted = callManager.handleMediaKey(false);
187  } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
188  // FIXME: We only consider power key presses if the screen is
189  // on because of bugs 1410830/1409003. The theory is that when
190  // those bugs are encountered, there is a >2s delay between the
191  // power press event and the power release event, which causes
192  // the shutdown dialog to appear on resume. So to avoid that
193  // symptom while we investigate the root cause, we simply won't
194  // initiate any dialogs when the screen is off.
195  if (Powerd.status === Powerd.On) {
196  dialogs.onPowerKeyPressed();
197  }
198  event.accepted = true;
199  } else {
200  volumeKeyFilter.onKeyPressed(event.key);
201  event.accepted = false;
202  }
203  }
204 
205  Keys.onReleased: {
206  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
207  dialogs.onPowerKeyReleased();
208  event.accepted = true;
209  } else {
210  volumeKeyFilter.onKeyReleased(event.key);
211  event.accepted = false;
212  }
213  }
214  }
215 
216  Item {
217  id: stages
218  objectName: "stages"
219  width: parent.width
220  height: parent.height
221  visible: !ApplicationManager.empty
222 
223  Connections {
224  target: ApplicationManager
225  onFocusRequested: {
226  if (greeter.narrowMode) {
227  if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
228  // If we are in the middle of a call, make dialer lockedApp and show it.
229  // This can happen if user backs out of dialer back to greeter, then
230  // launches dialer again.
231  greeter.lockedApp = appId;
232  }
233  if (greeter.hasLockedApp) {
234  if (appId === greeter.lockedApp) {
235  lockscreen.hide() // show locked app
236  } else {
237  greeter.startUnlock() // show lockscreen if necessary
238  }
239  }
240  greeter.hide();
241  } else {
242  if (LightDM.Greeter.active) {
243  greeter.startUnlock()
244  }
245  }
246  }
247 
248  onFocusedApplicationIdChanged: {
249  if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
250  greeter.startUnlock()
251  }
252  panel.indicators.hide();
253  }
254 
255  onApplicationAdded: {
256  if (appId != "unity8-dash") {
257  if (greeter.shown) {
258  greeter.startUnlock();
259  }
260 
261  // If this happens on first boot, we may be in edge
262  // tutorial or wizard while receiving a call. But a call
263  // is more important than wizard so just bail out of those.
264  if (tutorial.running) {
265  tutorial.finish();
266  wizard.hide();
267  }
268  }
269  if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
270  lockscreen.hide() // show locked app
271  }
272  launcher.hide();
273  }
274  }
275 
276  Loader {
277  id: applicationsDisplayLoader
278  objectName: "applicationsDisplayLoader"
279  anchors.fill: parent
280 
281  // When we have a locked app, we only want to show that one app.
282  // FIXME: do this in a less traumatic way. We currently only allow
283  // locked apps in phone mode (see FIXME in Lockscreen component in
284  // this same file). When that changes, we need to do something
285  // nicer here. But this code is currently just to prevent a
286  // theoretical attack where user enters lockedApp mode, then makes
287  // the screen larger (maybe connects to monitor) and tries to enter
288  // tablet mode.
289  property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
290  source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
291  : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
292 
293  property bool interactive: tutorial.spreadEnabled
294  && !greeter.shown
295  && !lockscreen.shown
296  && panel.indicators.fullyClosed
297  && launcher.progress == 0
298  && !notifications.useModal
299 
300  onInteractiveChanged: { if (interactive) { focus = true; } }
301 
302  Binding {
303  target: applicationsDisplayLoader.item
304  property: "objectName"
305  value: "stage"
306  }
307  Binding {
308  target: applicationsDisplayLoader.item
309  property: "dragAreaWidth"
310  value: shell.edgeSize
311  }
312  Binding {
313  target: applicationsDisplayLoader.item
314  property: "maximizedAppTopMargin"
315  // Not just using panel.panelHeight as that changes depending on the focused app.
316  value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
317  }
318  Binding {
319  target: applicationsDisplayLoader.item
320  property: "interactive"
321  value: applicationsDisplayLoader.interactive
322  }
323  Binding {
324  target: applicationsDisplayLoader.item
325  property: "spreadEnabled"
326  value: tutorial.spreadEnabled && !greeter.hasLockedApp
327  }
328  Binding {
329  target: applicationsDisplayLoader.item
330  property: "inverseProgress"
331  value: launcher.progress
332  }
333  Binding {
334  target: applicationsDisplayLoader.item
335  property: "orientation"
336  value: shell.orientation
337  }
338  Binding {
339  target: applicationsDisplayLoader.item
340  property: "background"
341  value: shell.background
342  }
343  }
344  }
345 
346  InputMethod {
347  id: inputMethod
348  objectName: "inputMethod"
349  anchors { fill: parent; topMargin: panel.panelHeight }
350  z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
351  }
352 
353  Connections {
354  target: SurfaceManager
355  onSurfaceCreated: {
356  if (surface.type == MirSurfaceItem.InputMethod) {
357  inputMethod.surface = surface;
358  }
359  }
360 
361  onSurfaceDestroyed: {
362  if (inputMethod.surface == surface) {
363  inputMethod.surface = null;
364  surface.parent = null;
365  }
366  if (!surface.parent) {
367  // there's no one displaying it. delete it right away
368  surface.release();
369  }
370  }
371  }
372  Connections {
373  target: SessionManager
374  onSessionStopping: {
375  if (!session.parentSession && !session.application) {
376  // nothing is using it. delete it right away
377  session.release();
378  }
379  }
380  }
381 
382  Lockscreen {
383  id: lockscreen
384  objectName: "lockscreen"
385 
386  hides: [launcher, panel.indicators]
387  shown: false
388  enabled: true
389  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
390  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
391  y: panel.panelHeight
392  visible: required
393  width: parent.width
394  height: parent.height - panel.panelHeight
395  background: shell.background
396  darkenBackground: 0.4
397  alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
398  minPinLength: 4
399  maxPinLength: 4
400 
401  property string promptText
402  infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
403  alphaNumeric ? i18n.tr("Enter passphrase") :
404  i18n.tr("Enter passcode")
405  errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
406  alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
407  i18n.tr("Please re-enter") :
408  i18n.tr("Sorry, incorrect passcode")
409 
410  // FIXME: We *should* show emergency dialer if there is a SIM present,
411  // regardless of whether the side stage is enabled. But right now,
412  // the assumption is that narrow screens are phones which have SIMs
413  // and wider screens are tablets which don't. When we do allow this
414  // on devices with a side stage and a SIM, work should be done to
415  // ensure that the main stage is disabled while the dialer is present
416  // in the side stage. See the FIXME in the stage loader in this file.
417  showEmergencyCallButton: !shell.sideStageEnabled
418 
419  onEntered: LightDM.Greeter.respond(passphrase);
420  onCancel: greeter.show()
421  onEmergencyCall: startLockedApp("dialer-app")
422 
423  onShownChanged: if (shown) greeter.lockedApp = ""
424 
425  function maybeShow() {
426  if (!shell.forcedUnlock && !shown) {
427  showNow();
428  }
429  }
430 
431  Timer {
432  id: forcedDelayTimer
433  interval: 1000 * 60
434  onTriggered: {
435  if (lockscreen.delayMinutes > 0) {
436  lockscreen.delayMinutes -= 1
437  if (lockscreen.delayMinutes > 0) {
438  start() // go again
439  }
440  }
441  }
442  }
443 
444  Component.onCompleted: {
445  if (greeter.narrowMode) {
446  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447  }
448  }
449  }
450 
451  Connections {
452  target: LightDM.Greeter
453 
454  onShowGreeter: greeter.show()
455  onHideGreeter: greeter.login()
456 
457  onShowPrompt: {
458  shell.enabled = true;
459  if (!LightDM.Greeter.active) {
460  return; // could happen if hideGreeter() comes in before we prompt
461  }
462  if (greeter.narrowMode) {
463  lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
464  lockscreen.maybeShow();
465  }
466  }
467 
468  onPromptlessChanged: {
469  if (!LightDM.Greeter.active) {
470  return; // could happen if hideGreeter() comes in before we prompt
471  }
472  if (greeter.narrowMode) {
473  if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
474  lockscreen.hide()
475  } else {
476  lockscreen.reset();
477  lockscreen.maybeShow();
478  }
479  }
480  }
481 
482  onAuthenticationComplete: {
483  shell.enabled = true;
484  if (LightDM.Greeter.authenticated) {
485  AccountsService.failedLogins = 0
486  }
487  // Else only penalize user for a failed login if they actually were
488  // prompted for a password. We do this below after the promptless
489  // early exit.
490 
491  if (LightDM.Greeter.promptless) {
492  return;
493  }
494 
495  if (LightDM.Greeter.authenticated) {
496  greeter.login();
497  } else {
498  AccountsService.failedLogins++
499  if (maxFailedLogins >= 2) { // require at least a warning
500  if (AccountsService.failedLogins === maxFailedLogins - 1) {
501  var title = lockscreen.alphaNumeric ?
502  i18n.tr("Sorry, incorrect passphrase.") :
503  i18n.tr("Sorry, incorrect passcode.")
504  var text = i18n.tr("This will be your last attempt.") + " " +
505  (lockscreen.alphaNumeric ?
506  i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
507  i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
508  lockscreen.showInfoPopup(title, text)
509  } else if (AccountsService.failedLogins >= maxFailedLogins) {
510  SystemImage.factoryReset() // Ouch!
511  }
512  }
513  if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
514  lockscreen.delayMinutes = failedLoginsDelayMinutes
515  forcedDelayTimer.start()
516  }
517 
518  lockscreen.clear(true);
519  if (greeter.narrowMode) {
520  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
521  }
522  }
523  }
524  }
525 
526  Binding {
527  target: LightDM.Greeter
528  property: "active"
529  value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
530  }
531 
532  Rectangle {
533  anchors.fill: parent
534  color: "black"
535  opacity: greeterWrapper.showProgress * 0.8
536  }
537 
538  Item {
539  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
540  id: greeterWrapper
541  objectName: "greeterWrapper"
542  x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
543  y: panel.panelHeight
544  width: parent.width
545  height: parent.height - panel.panelHeight
546 
547  Behavior on x {
548  enabled: !launcher.dashSwipe
549  StandardAnimation {}
550  }
551 
552  property bool fullyShown: showProgress === 1.0
553  onFullyShownChanged: {
554  // Wait until the greeter is completely covering lockscreen before resetting it.
555  if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
556  lockscreen.reset();
557  lockscreen.maybeShow();
558  }
559  }
560 
561  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
562  onShowProgressChanged: {
563  if (showProgress === 0) {
564  if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
565  greeter.login()
566  } else if (greeter.narrowMode) {
567  lockscreen.clear(false) // to reset focus if necessary
568  }
569  }
570  }
571 
572  Greeter {
573  id: greeter
574  objectName: "greeter"
575 
576  signal sessionStarted() // helpful for tests
577 
578  property string lockedApp: ""
579  property bool hasLockedApp: lockedApp !== ""
580 
581  hides: [launcher, panel.indicators]
582  loadContent: required || lockscreen.required // keeps content in memory for quick show()
583 
584  locked: shell.locked
585 
586  background: shell.background
587 
588  width: parent.width
589  height: parent.height
590 
591 
592  // avoid overlapping with Launcher's edge drag area
593  // FIXME: Fix TouchRegistry & friends and remove this workaround
594  // Issue involves launcher's DDA getting disabled on a long
595  // left-edge drag
596  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
597 
598  function startUnlock() {
599  if (narrowMode) {
600  if (!LightDM.Greeter.authenticated) {
601  lockscreen.maybeShow()
602  }
603  hide()
604  } else {
605  show()
606  tryToUnlock()
607  }
608  }
609 
610  function login() {
611  enabled = false;
612  if (LightDM.Greeter.startSessionSync()) {
613  sessionStarted();
614  greeter.hide();
615  lockscreen.hide();
616  launcher.hide();
617  }
618  enabled = true;
619  }
620 
621  Timer {
622  // See powerConnection for why this is useful
623  id: showGreeterDelayed
624  interval: 1
625  onTriggered: {
626  greeter.showNow();
627  }
628  }
629 
630  onShownChanged: {
631  if (shown) {
632  // Disable everything so that user can't swipe greeter or
633  // launcher until we get the next prompt/authenticate, which
634  // will re-enable the shell.
635  shell.enabled = false;
636 
637  if (greeter.narrowMode) {
638  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
639  } else {
640  reset()
641  }
642  greeter.lockedApp = "";
643  greeter.forceActiveFocus();
644  }
645  }
646 
647  Component.onCompleted: {
648  Connectivity.unlockAllModems()
649  }
650 
651  onUnlocked: greeter.hide()
652  onSelected: {
653  // Update launcher items for new user
654  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
655  AccountsService.user = user;
656  LauncherModel.setUser(user);
657  }
658 
659  onTapped: {
660  if (!tutorial.running) {
661  launcher.tease();
662  }
663  }
664  onDraggingChanged: {
665  if (dragging && !tutorial.running) {
666  launcher.tease();
667  }
668  }
669 
670  Binding {
671  target: ApplicationManager
672  property: "suspended"
673  value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
674  }
675  }
676  }
677 
678  Connections {
679  id: callConnection
680  target: callManager
681 
682  onHasCallsChanged: {
683  if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
684  // We just received an incoming call while locked. The
685  // indicator will have already launched dialer-app for us, but
686  // there is a race between "hasCalls" changing and the dialer
687  // starting up. So in case we lose that race, we'll start/
688  // focus the dialer ourselves here too. Even if the indicator
689  // didn't launch the dialer for some reason (or maybe a call
690  // started via some other means), if an active call is
691  // happening, we want to be in the dialer.
692  startLockedApp("dialer-app")
693  }
694  }
695  }
696 
697  Connections {
698  id: powerConnection
699  target: Powerd
700 
701  onStatusChanged: {
702  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
703  !callManager.hasCalls && !tutorial.running) {
704  // We don't want to simply call greeter.showNow() here, because
705  // that will take too long. Qt will delay button event
706  // handling until the greeter is done loading and may think the
707  // user held down the power button the whole time, leading to a
708  // power dialog being shown. Instead, delay showing the
709  // greeter until we've finished handling the event. We could
710  // make the greeter load asynchronously instead, but that
711  // introduces a whole host of timing issues, especially with
712  // its animations. So this is simpler.
713  showGreeterDelayed.start();
714  }
715  }
716  }
717 
718  function showHome() {
719  if (tutorial.running) {
720  return
721  }
722 
723  if (LightDM.Greeter.active) {
724  greeter.startUnlock()
725  }
726 
727  var animate = !LightDM.Greeter.active && !stages.shown
728  dash.setCurrentScope(0, animate, false)
729  ApplicationManager.requestFocusApplication("unity8-dash")
730  }
731 
732  function showDash() {
733  if (greeter.hasLockedApp || // just in case user gets here
734  (!greeter.narrowMode && shell.locked)) {
735  return
736  }
737 
738  if (greeter.shown) {
739  greeter.hideRight();
740  launcher.fadeOut();
741  }
742 
743  if (ApplicationManager.focusedApplicationId != "unity8-dash") {
744  ApplicationManager.requestFocusApplication("unity8-dash")
745  launcher.fadeOut();
746  }
747  }
748 
749  Item {
750  id: overlay
751  z: 10
752 
753  anchors.fill: parent
754 
755  Panel {
756  id: panel
757  objectName: "panel"
758  anchors.fill: parent //because this draws indicator menus
759  indicators {
760  hides: [launcher]
761  available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
762  contentEnabled: tutorial.panelContentEnabled
763  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
764 
765  minimizedPanelHeight: units.gu(3)
766  expandedPanelHeight: units.gu(7)
767 
768  indicatorsModel: Indicators.IndicatorsModel {
769  // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
770  profile: indicatorProfile
771  Component.onCompleted: load()
772  }
773  }
774  callHint {
775  greeterShown: greeter.shown || lockscreen.shown
776  }
777 
778  property bool topmostApplicationIsFullscreen:
779  ApplicationManager.focusedApplicationId &&
780  ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
781 
782  fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
783  || greeter.hasLockedApp
784  }
785 
786  Launcher {
787  id: launcher
788  objectName: "launcher"
789 
790  readonly property bool dashSwipe: progress > 0
791 
792  anchors.top: parent.top
793  anchors.topMargin: inverted ? 0 : panel.panelHeight
794  anchors.bottom: parent.bottom
795  width: parent.width
796  dragAreaWidth: shell.edgeSize
797  available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
798  inverted: usageModeSettings.usageMode === "Staged"
799  shadeBackground: !tutorial.running
800 
801  onShowDashHome: showHome()
802  onDash: showDash()
803  onDashSwipeChanged: {
804  if (dashSwipe) {
805  dash.setCurrentScope(0, false, true)
806  }
807  }
808  onLauncherApplicationSelected: {
809  if (greeter.hasLockedApp) {
810  greeter.startUnlock()
811  }
812  if (!tutorial.running)
813  shell.activateApplication(appId)
814  }
815  onShownChanged: {
816  if (shown) {
817  panel.indicators.hide()
818  }
819  }
820  }
821 
822  Wizard {
823  id: wizard
824  anchors.fill: parent
825  background: shell.background
826  }
827 
828  Rectangle {
829  id: modalNotificationBackground
830 
831  visible: notifications.useModal && (notifications.state == "narrow")
832  color: "#000000"
833  anchors.fill: parent
834  opacity: 0.9
835 
836  MouseArea {
837  anchors.fill: parent
838  }
839  }
840 
841  Notifications {
842  id: notifications
843 
844  model: NotificationBackend.Model
845  margin: units.gu(1)
846 
847  y: topmostIsFullscreen ? 0 : panel.panelHeight
848  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
849 
850  states: [
851  State {
852  name: "narrow"
853  when: overlay.width <= units.gu(60)
854  AnchorChanges {
855  target: notifications
856  anchors.left: parent.left
857  anchors.right: parent.right
858  }
859  },
860  State {
861  name: "wide"
862  when: overlay.width > units.gu(60)
863  AnchorChanges {
864  target: notifications
865  anchors.left: undefined
866  anchors.right: parent.right
867  }
868  PropertyChanges { target: notifications; width: units.gu(38) }
869  }
870  ]
871  }
872  }
873 
874  Dialogs {
875  id: dialogs
876  anchors.fill: parent
877  z: overlay.z + 10
878  onPowerOffClicked: {
879  shutdownFadeOutRectangle.enabled = true;
880  shutdownFadeOutRectangle.visible = true;
881  shutdownFadeOut.start();
882  }
883  }
884 
885  Tutorial {
886  id: tutorial
887  objectName: "tutorial"
888  active: AccountsService.demoEdges
889  paused: LightDM.Greeter.active
890  launcher: launcher
891  panel: panel
892  stages: stages
893  overlay: overlay
894  edgeSize: shell.edgeSize
895 
896  onFinished: {
897  AccountsService.demoEdges = false;
898  active = false; // for immediate response / if AS is having problems
899  }
900  }
901 
902  Connections {
903  target: SessionBroadcast
904  onShowHome: showHome()
905  }
906 
907  Rectangle {
908  id: shutdownFadeOutRectangle
909  z: screenGrabber.z + 10
910  enabled: false
911  visible: false
912  color: "black"
913  anchors.fill: parent
914  opacity: 0.0
915  NumberAnimation on opacity {
916  id: shutdownFadeOut
917  from: 0.0
918  to: 1.0
919  onStopped: {
920  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
921  DBusUnitySessionService.Shutdown();
922  }
923  }
924  }
925  }
926 
927 }