on 65bdb6d help sign in
unity8/qml/Shell.qml
805 lines 28 kB history raw
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 */
17import QtQuick 2.0
18import QtQuick.Window 2.0
19import AccountsService 0.1
20import GSettings 1.0
21import Unity.Application 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
29import Utils 0.1
30import LightDM 0.1 as LightDM
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stages"
39import "Panel/Indicators"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
44Item {
45 id: shell
47 // this is only here to select the width / height of the window if not running fullscreen
48 property bool tablet: false
49 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
50 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
52 property real edgeSize: units.gu(2)
53 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
54 property url background
55 readonly property real panelHeight: panel.panelHeight
57 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
58 readonly property bool forcedUnlock: edgeDemo.running
59 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
61 property bool sideStageEnabled: shell.width >= units.gu(100)
62 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
64 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
65 property int failedLoginsDelayAttempts: 7 // number of failed logins
66 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
68 property int orientation
69 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
70 onDeviceOrientationAngleChanged: {
71 if (!OrientationLock.enabled) {
72 orientation = Screen.orientation;
73 }
74 }
75 readonly property bool orientationLockEnabled: OrientationLock.enabled
76 onOrientationLockEnabledChanged: {
77 if (orientationLockEnabled) {
78 OrientationLock.savedOrientation = Screen.orientation;
79 } else {
80 orientation = Screen.orientation;
81 }
82 }
84 function activateApplication(appId) {
85 if (ApplicationManager.findApplication(appId)) {
86 ApplicationManager.requestFocusApplication(appId);
87 } else {
88 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
89 ApplicationManager.startApplication(appId, execFlags);
90 }
91 }
93 function startLockedApp(app) {
94 if (!shell.locked) {
95 console.warn("Called startLockedApp(%1) when not locked, ignoring".arg(app))
96 return
97 }
98 greeter.lockedApp = app
99 shell.activateApplication(app)
100 }
102 Binding {
103 target: LauncherModel
104 property: "applicationManager"
105 value: ApplicationManager
106 }
108 Component.onCompleted: {
109 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
110 if (ApplicationManager.count > 0) {
111 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
112 }
113 if (orientationLockEnabled) {
114 orientation = OrientationLock.savedOrientation;
115 }
116 }
118 GSettings {
119 id: backgroundSettings
120 schema.id: "org.gnome.desktop.background"
121 }
122 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
123 onGSettingsPictureChanged: {
124 shell.background = gSettingsPicture
125 }
127 VolumeControl {
128 id: volumeControl
129 }
131 DashCommunicator {
132 id: dash
133 objectName: "dashCommunicator"
134 }
136 Binding {
137 target: ApplicationManager
138 property: "forceDashActive"
139 value: launcher.shown || launcher.dashSwipe
140 }
143 WindowKeysFilter {
144 // Handle but do not filter out volume keys
145 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
146 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
148 Keys.onPressed: {
149 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
150 dialogs.onPowerKeyPressed();
151 event.accepted = true;
152 } else {
153 event.accepted = false;
154 }
155 }
157 Keys.onReleased: {
158 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
159 dialogs.onPowerKeyReleased();
160 event.accepted = true;
161 } else {
162 event.accepted = false;
163 }
164 }
165 }
167 Item {
168 id: stages
169 objectName: "stages"
170 width: parent.width
171 height: parent.height
172 visible: !ApplicationManager.empty
174 Connections {
175 target: ApplicationManager
176 onFocusRequested: {
177 if (greeter.narrowMode) {
178 if (appId === "dialer-app" && callManager.hasCalls) {
179 // If we are in the middle of a call, make dialer lockedApp and show it.
180 // This can happen if user backs out of dialer back to greeter, then
181 // launches dialer again.
182 greeter.lockedApp = appId;
183 }
184 if (greeter.hasLockedApp) {
185 if (appId === greeter.lockedApp) {
186 lockscreen.hide() // show locked app
187 } else {
188 greeter.startUnlock() // show lockscreen if necessary
189 }
190 }
191 greeter.hide();
192 } else {
193 if (LightDM.Greeter.active) {
194 greeter.startUnlock()
195 }
196 }
197 }
199 onFocusedApplicationIdChanged: {
200 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
201 greeter.startUnlock()
202 }
203 panel.indicators.hide();
204 }
206 onApplicationAdded: {
207 if (greeter.shown && appId != "unity8-dash") {
208 greeter.startUnlock()
209 }
210 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
211 lockscreen.hide() // show locked app
212 }
213 launcher.hide();
214 }
215 }
217 Loader {
218 id: applicationsDisplayLoader
219 objectName: "applicationsDisplayLoader"
220 anchors.fill: parent
222 // When we have a locked app, we only want to show that one app.
223 // FIXME: do this in a less traumatic way. We currently only allow
224 // locked apps in phone mode (see FIXME in Lockscreen component in
225 // this same file). When that changes, we need to do something
226 // nicer here. But this code is currently just to prevent a
227 // theoretical attack where user enters lockedApp mode, then makes
228 // the screen larger (maybe connects to monitor) and tries to enter
229 // tablet mode.
230 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
231 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
233 Binding {
234 target: applicationsDisplayLoader.item
235 property: "objectName"
236 value: "stage"
237 }
238 Binding {
239 target: applicationsDisplayLoader.item
240 property: "dragAreaWidth"
241 value: shell.edgeSize
242 }
243 Binding {
244 target: applicationsDisplayLoader.item
245 property: "maximizedAppTopMargin"
246 // Not just using panel.panelHeight as that changes depending on the focused app.
247 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
248 }
249 Binding {
250 target: applicationsDisplayLoader.item
251 property: "interactive"
252 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
253 }
254 Binding {
255 target: applicationsDisplayLoader.item
256 property: "spreadEnabled"
257 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
258 }
259 Binding {
260 target: applicationsDisplayLoader.item
261 property: "inverseProgress"
262 value: launcher.progress
263 }
264 Binding {
265 target: applicationsDisplayLoader.item
266 property: "orientation"
267 value: shell.orientation
268 }
269 }
270 }
272 InputMethod {
273 id: inputMethod
274 objectName: "inputMethod"
275 anchors { fill: parent; topMargin: panel.panelHeight }
276 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
277 }
279 Connections {
280 target: SurfaceManager
281 onSurfaceCreated: {
282 if (surface.type == MirSurfaceItem.InputMethod) {
283 inputMethod.surface = surface;
284 }
285 }
287 onSurfaceDestroyed: {
288 if (inputMethod.surface == surface) {
289 inputMethod.surface = null;
290 surface.parent = null;
291 }
292 if (!surface.parent) {
293 // there's no one displaying it. delete it right away
294 surface.release();
295 }
296 }
297 }
298 Connections {
299 target: SessionManager
300 onSessionStopping: {
301 if (!session.parentSession && !session.application) {
302 // nothing is using it. delete it right away
303 session.release();
304 }
305 }
306 }
308 Lockscreen {
309 id: lockscreen
310 objectName: "lockscreen"
312 hides: [launcher, panel.indicators]
313 shown: false
314 enabled: true
315 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
316 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
317 y: panel.panelHeight
318 visible: required
319 width: parent.width
320 height: parent.height - panel.panelHeight
321 background: shell.background
322 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
323 minPinLength: 4
324 maxPinLength: 4
326 // FIXME: We *should* show emergency dialer if there is a SIM present,
327 // regardless of whether the side stage is enabled. But right now,
328 // the assumption is that narrow screens are phones which have SIMs
329 // and wider screens are tablets which don't. When we do allow this
330 // on devices with a side stage and a SIM, work should be done to
331 // ensure that the main stage is disabled while the dialer is present
332 // in the side stage. See the FIXME in the stage loader in this file.
333 showEmergencyCallButton: !shell.sideStageEnabled
335 onEntered: LightDM.Greeter.respond(passphrase);
336 onCancel: greeter.show()
337 onEmergencyCall: startLockedApp("dialer-app")
339 onShownChanged: if (shown) greeter.lockedApp = ""
341 function maybeShow() {
342 if (!shell.forcedUnlock) {
343 show()
344 }
345 }
347 Timer {
348 id: forcedDelayTimer
349 interval: 1000 * 60
350 onTriggered: {
351 if (lockscreen.delayMinutes > 0) {
352 lockscreen.delayMinutes -= 1
353 if (lockscreen.delayMinutes > 0) {
354 start() // go again
355 }
356 }
357 }
358 }
360 Component.onCompleted: {
361 if (greeter.narrowMode) {
362 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
363 }
364 }
365 }
367 Connections {
368 target: LightDM.Greeter
370 onShowGreeter: greeter.show()
371 onHideGreeter: greeter.login()
373 onShowPrompt: {
374 if (greeter.narrowMode) {
375 if (isDefaultPrompt) {
376 if (lockscreen.alphaNumeric) {
377 lockscreen.infoText = i18n.tr("Enter passphrase")
378 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
379 i18n.tr("Please re-enter")
380 } else {
381 lockscreen.infoText = i18n.tr("Enter passcode")
382 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
383 }
384 } else {
385 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
386 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
387 }
389 lockscreen.maybeShow();
390 }
391 }
393 onPromptlessChanged: {
394 if (greeter.narrowMode) {
395 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
396 lockscreen.hide()
397 } else {
398 lockscreen.reset();
399 lockscreen.maybeShow();
400 }
401 }
402 }
404 onAuthenticationComplete: {
405 if (LightDM.Greeter.authenticated) {
406 AccountsService.failedLogins = 0
407 }
408 // Else only penalize user for a failed login if they actually were
409 // prompted for a password. We do this below after the promptless
410 // early exit.
412 if (LightDM.Greeter.promptless) {
413 return;
414 }
416 if (LightDM.Greeter.authenticated) {
417 greeter.login();
418 } else {
419 AccountsService.failedLogins++
420 if (maxFailedLogins >= 2) { // require at least a warning
421 if (AccountsService.failedLogins === maxFailedLogins - 1) {
422 var title = lockscreen.alphaNumeric ?
423 i18n.tr("Sorry, incorrect passphrase.") :
424 i18n.tr("Sorry, incorrect passcode.")
425 var text = i18n.tr("This will be your last attempt.") + " " +
426 (lockscreen.alphaNumeric ?
427 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
428 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
429 lockscreen.showInfoPopup(title, text)
430 } else if (AccountsService.failedLogins >= maxFailedLogins) {
431 SystemImage.factoryReset() // Ouch!
432 }
433 }
434 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
435 lockscreen.delayMinutes = failedLoginsDelayMinutes
436 forcedDelayTimer.start()
437 }
439 lockscreen.clear(true);
440 if (greeter.narrowMode) {
441 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
442 }
443 }
444 }
445 }
447 Binding {
448 target: LightDM.Greeter
449 property: "active"
450 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
451 }
453 Rectangle {
454 anchors.fill: parent
455 color: "black"
456 opacity: greeterWrapper.showProgress * 0.8
457 }
459 Item {
460 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
461 id: greeterWrapper
462 objectName: "greeterWrapper"
463 x: greeter.narrowMode ? launcher.progress : 0
464 y: panel.panelHeight
465 width: parent.width
466 height: parent.height - panel.panelHeight
468 Behavior on x {
469 enabled: !launcher.dashSwipe
470 StandardAnimation {}
471 }
473 property bool fullyShown: showProgress === 1.0
474 onFullyShownChanged: {
475 // Wait until the greeter is completely covering lockscreen before resetting it.
476 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
477 lockscreen.reset();
478 lockscreen.maybeShow();
479 }
480 }
482 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
483 onShowProgressChanged: {
484 if (showProgress === 0) {
485 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
486 greeter.login()
487 } else if (greeter.narrowMode) {
488 lockscreen.clear(false) // to reset focus if necessary
489 }
490 }
491 }
493 Greeter {
494 id: greeter
495 objectName: "greeter"
497 signal sessionStarted() // helpful for tests
499 property string lockedApp: ""
500 property bool hasLockedApp: lockedApp !== ""
502 available: true
503 hides: [launcher, panel.indicators]
504 shown: true
505 loadContent: required || lockscreen.required // keeps content in memory for quick show()
507 locked: shell.locked
509 defaultBackground: shell.background
511 width: parent.width
512 height: parent.height
514 dragHandleWidth: shell.edgeSize
516 function startUnlock() {
517 if (narrowMode) {
518 if (!LightDM.Greeter.authenticated) {
519 lockscreen.maybeShow()
520 }
521 hide()
522 } else {
523 show()
524 tryToUnlock()
525 }
526 }
528 function login() {
529 enabled = false;
530 if (LightDM.Greeter.startSessionSync()) {
531 sessionStarted();
532 greeter.hide();
533 lockscreen.hide();
534 launcher.hide();
535 }
536 enabled = true;
537 }
539 onShownChanged: {
540 if (shown) {
541 if (greeter.narrowMode) {
542 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
543 } else {
544 reset()
545 }
546 greeter.lockedApp = "";
547 greeter.forceActiveFocus();
548 }
549 }
551 Component.onCompleted: {
552 Connectivity.unlockAllModems()
553 }
555 onUnlocked: greeter.hide()
556 onSelected: {
557 // Update launcher items for new user
558 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
559 AccountsService.user = user;
560 LauncherModel.setUser(user);
561 }
563 onTease: launcher.tease()
565 Binding {
566 target: ApplicationManager
567 property: "suspended"
568 value: greeter.shown && greeterWrapper.showProgress == 1
569 }
570 }
571 }
573 Connections {
574 id: callConnection
575 target: callManager
577 onHasCallsChanged: {
578 if (shell.locked && callManager.hasCalls) {
579 // We just received an incoming call while locked. The
580 // indicator will have already launched dialer-app for us, but
581 // there is a race between "hasCalls" changing and the dialer
582 // starting up. So in case we lose that race, we'll start/
583 // focus the dialer ourselves here too. Even if the indicator
584 // didn't launch the dialer for some reason (or maybe a call
585 // started via some other means), if an active call is
586 // happening, we want to be in the dialer.
587 startLockedApp("dialer-app")
588 }
589 }
590 }
592 Connections {
593 id: powerConnection
594 target: Powerd
596 onStatusChanged: {
597 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
598 greeter.showNow()
599 }
600 }
601 }
603 function showHome() {
604 if (edgeDemo.running) {
605 return
606 }
608 if (LightDM.Greeter.active) {
609 greeter.startUnlock()
610 }
612 var animate = !LightDM.Greeter.active && !stages.shown
613 dash.setCurrentScope("clickscope", animate, false)
614 ApplicationManager.requestFocusApplication("unity8-dash")
615 }
617 function showDash() {
618 if (greeter.hasLockedApp || // just in case user gets here
619 (!greeter.narrowMode && shell.locked)) {
620 return
621 }
623 if (greeter.shown) {
624 greeter.hideRight();
625 launcher.fadeOut();
626 }
628 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
629 ApplicationManager.requestFocusApplication("unity8-dash")
630 launcher.fadeOut();
631 }
632 }
634 Item {
635 id: overlay
636 z: 10
638 anchors.fill: parent
640 Panel {
641 id: panel
642 objectName: "panel"
643 anchors.fill: parent //because this draws indicator menus
644 indicators {
645 hides: [launcher]
646 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
647 contentEnabled: edgeDemo.panelContentEnabled
648 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
650 minimizedPanelHeight: units.gu(3)
651 expandedPanelHeight: units.gu(7)
653 indicatorsModel: visibleIndicators.model
654 }
656 VisibleIndicators {
657 id: visibleIndicators
658 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
659 Component.onCompleted: initialise(indicatorProfile)
660 }
662 property bool topmostApplicationIsFullscreen:
663 ApplicationManager.focusedApplicationId &&
664 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
666 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
667 || greeter.hasLockedApp
668 }
670 Launcher {
671 id: launcher
672 objectName: "launcher"
674 readonly property bool dashSwipe: progress > 0
676 anchors.top: parent.top
677 anchors.bottom: parent.bottom
678 width: parent.width
679 dragAreaWidth: shell.edgeSize
680 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
682 onShowDashHome: showHome()
683 onDash: showDash()
684 onDashSwipeChanged: {
685 if (dashSwipe) {
686 dash.setCurrentScope("clickscope", false, true)
687 }
688 }
689 onLauncherApplicationSelected: {
690 if (greeter.hasLockedApp) {
691 greeter.startUnlock()
692 }
693 if (!edgeDemo.running)
694 shell.activateApplication(appId)
695 }
696 onShownChanged: {
697 if (shown) {
698 panel.indicators.hide()
699 }
700 }
701 }
703 Rectangle {
704 id: modalNotificationBackground
706 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
707 color: "#000000"
708 anchors.fill: parent
709 opacity: 0.9
711 MouseArea {
712 anchors.fill: parent
713 }
714 }
716 Notifications {
717 id: notifications
719 model: NotificationBackend.Model
720 margin: units.gu(1)
722 y: topmostIsFullscreen ? 0 : panel.panelHeight
723 width: parent.width
724 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
726 states: [
727 State {
728 name: "narrow"
729 when: overlay.width <= units.gu(60)
730 AnchorChanges { target: notifications; anchors.left: parent.left }
731 },
732 State {
733 name: "wide"
734 when: overlay.width > units.gu(60)
735 AnchorChanges { target: notifications; anchors.left: undefined }
736 PropertyChanges { target: notifications; width: units.gu(38) }
737 }
738 ]
739 }
740 }
742 Dialogs {
743 id: dialogs
744 anchors.fill: parent
745 z: overlay.z + 10
746 onPowerOffClicked: {
747 shutdownFadeOutRectangle.enabled = true;
748 shutdownFadeOutRectangle.visible = true;
749 shutdownFadeOut.start();
750 }
751 }
753 Label {
754 id: alphaDisclaimerLabel
755 anchors.centerIn: parent
756 visible: ApplicationManager.fake ? ApplicationManager.fake : false
757 z: dialogs.z + 10
758 text: "EARLY ALPHA\nNOT READY FOR USE"
759 color: "lightgrey"
760 opacity: 0.2
761 font.weight: Font.Black
762 horizontalAlignment: Text.AlignHCenter
763 verticalAlignment: Text.AlignVCenter
764 fontSizeMode: Text.Fit
765 rotation: -45
766 scale: Math.min(parent.width, parent.height) / width
767 }
769 EdgeDemo {
770 id: edgeDemo
771 objectName: "edgeDemo"
772 z: alphaDisclaimerLabel.z + 10
773 paused: Powerd.status === Powerd.Off // Saves power
774 greeter: greeter
775 launcher: launcher
776 panel: panel
777 stages: stages
778 }
780 Connections {
781 target: SessionBroadcast
782 onShowHome: showHome()
783 }
785 Rectangle {
786 id: shutdownFadeOutRectangle
787 z: edgeDemo.z + 10
788 enabled: false
789 visible: false
790 color: "black"
791 anchors.fill: parent
792 opacity: 0.0
793 NumberAnimation on opacity {
794 id: shutdownFadeOut
795 from: 0.0
796 to: 1.0
797 onStopped: {
798 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
799 DBusUnitySessionService.Shutdown();
800 }
801 }
802 }
803 }