on af1f8ef help sign in
unity8/qml/Shell.qml
912 lines 33 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 "Tutorial"
40import "Wizard"
41import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Session 0.1
43import Unity.DashCommunicator 0.1
44import Unity.Indicators 0.1 as Indicators
46Item {
47 id: shell
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
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)
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
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()
69 property bool sideStageEnabled: shell.width >= units.gu(100)
70 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
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
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 }
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 }
101 function startLockedApp(app) {
102 if (shell.locked) {
103 greeter.lockedApp = app;
104 }
105 shell.activateApplication(app);
106 }
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 }
118 GSettings {
119 id: backgroundSettings
120 schema.id: "org.gnome.desktop.background"
121 }
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 }
133 GSettings {
134 id: usageModeSettings
135 schema.id: "com.canonical.Unity8"
136 }
138 Binding {
139 target: LauncherModel
140 property: "applicationManager"
141 value: ApplicationManager
142 }
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 }
154 VolumeControl {
155 id: volumeControl
156 }
158 DashCommunicator {
159 id: dash
160 objectName: "dashCommunicator"
161 }
163 ScreenGrabber {
164 id: screenGrabber
165 z: dialogs.z + 10
166 enabled: Powerd.status === Powerd.On
167 }
169 Binding {
170 target: ApplicationManager
171 property: "forceDashActive"
172 value: launcher.shown || launcher.dashSwipe
173 }
175 VolumeKeyFilter {
176 id: volumeKeyFilter
177 onVolumeDownPressed: volumeControl.volumeDown()
178 onVolumeUpPressed: volumeControl.volumeUp()
179 onBothVolumeKeysPressed: screenGrabber.capture()
180 }
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 }
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 }
216 Item {
217 id: stages
218 objectName: "stages"
219 width: parent.width
220 height: parent.height
221 visible: !ApplicationManager.empty
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 }
248 onFocusedApplicationIdChanged: {
249 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
250 greeter.startUnlock()
251 }
252 panel.indicators.hide();
253 }
255 onApplicationAdded: {
256 if (appId != "unity8-dash") {
257 if (greeter.shown) {
258 greeter.startUnlock();
259 }
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 }
276 Loader {
277 id: applicationsDisplayLoader
278 objectName: "applicationsDisplayLoader"
279 anchors.fill: parent
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"
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "objectName"
296 value: "stage"
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "dragAreaWidth"
301 value: shell.edgeSize
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "maximizedAppTopMargin"
306 // Not just using panel.panelHeight as that changes depending on the focused app.
307 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
308 }
309 Binding {
310 target: applicationsDisplayLoader.item
311 property: "interactive"
312 value: tutorial.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
313 }
314 Binding {
315 target: applicationsDisplayLoader.item
316 property: "spreadEnabled"
317 value: tutorial.stagesEnabled && !greeter.hasLockedApp
318 }
319 Binding {
320 target: applicationsDisplayLoader.item
321 property: "inverseProgress"
322 value: launcher.progress
323 }
324 Binding {
325 target: applicationsDisplayLoader.item
326 property: "orientation"
327 value: shell.orientation
328 }
329 Binding {
330 target: applicationsDisplayLoader.item
331 property: "background"
332 value: shell.background
333 }
334 }
335 }
337 InputMethod {
338 id: inputMethod
339 objectName: "inputMethod"
340 anchors { fill: parent; topMargin: panel.panelHeight }
341 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
342 }
344 Connections {
345 target: SurfaceManager
346 onSurfaceCreated: {
347 if (surface.type == MirSurfaceItem.InputMethod) {
348 inputMethod.surface = surface;
349 }
350 }
352 onSurfaceDestroyed: {
353 if (inputMethod.surface == surface) {
354 inputMethod.surface = null;
355 surface.parent = null;
356 }
357 if (!surface.parent) {
358 // there's no one displaying it. delete it right away
359 surface.release();
360 }
361 }
362 }
363 Connections {
364 target: SessionManager
365 onSessionStopping: {
366 if (!session.parentSession && !session.application) {
367 // nothing is using it. delete it right away
368 session.release();
369 }
370 }
371 }
373 Lockscreen {
374 id: lockscreen
375 objectName: "lockscreen"
377 hides: [launcher, panel.indicators]
378 shown: false
379 enabled: true
380 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
381 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
382 y: panel.panelHeight
383 visible: required
384 width: parent.width
385 height: parent.height - panel.panelHeight
386 background: shell.background
387 darkenBackground: 0.4
388 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
389 minPinLength: 4
390 maxPinLength: 4
392 property string promptText
393 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
394 alphaNumeric ? i18n.tr("Enter passphrase") :
395 i18n.tr("Enter passcode")
396 errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
397 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
398 i18n.tr("Please re-enter") :
399 i18n.tr("Sorry, incorrect passcode")
401 // FIXME: We *should* show emergency dialer if there is a SIM present,
402 // regardless of whether the side stage is enabled. But right now,
403 // the assumption is that narrow screens are phones which have SIMs
404 // and wider screens are tablets which don't. When we do allow this
405 // on devices with a side stage and a SIM, work should be done to
406 // ensure that the main stage is disabled while the dialer is present
407 // in the side stage. See the FIXME in the stage loader in this file.
408 showEmergencyCallButton: !shell.sideStageEnabled
410 onEntered: LightDM.Greeter.respond(passphrase);
411 onCancel: greeter.show()
412 onEmergencyCall: startLockedApp("dialer-app")
414 onShownChanged: if (shown) greeter.lockedApp = ""
416 function maybeShow() {
417 if (!shell.forcedUnlock) {
418 showNow();
419 }
420 }
422 Timer {
423 id: forcedDelayTimer
424 interval: 1000 * 60
425 onTriggered: {
426 if (lockscreen.delayMinutes > 0) {
427 lockscreen.delayMinutes -= 1
428 if (lockscreen.delayMinutes > 0) {
429 start() // go again
430 }
431 }
432 }
433 }
435 Component.onCompleted: {
436 if (greeter.narrowMode) {
437 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
438 }
439 }
440 }
442 Connections {
443 target: LightDM.Greeter
445 onShowGreeter: greeter.show()
446 onHideGreeter: greeter.login()
448 onShowPrompt: {
449 shell.enabled = true;
450 if (!LightDM.Greeter.active) {
451 return; // could happen if hideGreeter() comes in before we prompt
452 }
453 if (greeter.narrowMode) {
454 lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
455 lockscreen.maybeShow();
456 }
457 }
459 onPromptlessChanged: {
460 if (!LightDM.Greeter.active) {
461 return; // could happen if hideGreeter() comes in before we prompt
462 }
463 if (greeter.narrowMode) {
464 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
465 lockscreen.hide()
466 } else {
467 lockscreen.reset();
468 lockscreen.maybeShow();
469 }
470 }
471 }
473 onAuthenticationComplete: {
474 shell.enabled = true;
475 if (LightDM.Greeter.authenticated) {
476 AccountsService.failedLogins = 0
477 }
478 // Else only penalize user for a failed login if they actually were
479 // prompted for a password. We do this below after the promptless
480 // early exit.
482 if (LightDM.Greeter.promptless) {
483 return;
484 }
486 if (LightDM.Greeter.authenticated) {
487 greeter.login();
488 } else {
489 AccountsService.failedLogins++
490 if (maxFailedLogins >= 2) { // require at least a warning
491 if (AccountsService.failedLogins === maxFailedLogins - 1) {
492 var title = lockscreen.alphaNumeric ?
493 i18n.tr("Sorry, incorrect passphrase.") :
494 i18n.tr("Sorry, incorrect passcode.")
495 var text = i18n.tr("This will be your last attempt.") + " " +
496 (lockscreen.alphaNumeric ?
497 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
498 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
499 lockscreen.showInfoPopup(title, text)
500 } else if (AccountsService.failedLogins >= maxFailedLogins) {
501 SystemImage.factoryReset() // Ouch!
502 }
503 }
504 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
505 lockscreen.delayMinutes = failedLoginsDelayMinutes
506 forcedDelayTimer.start()
507 }
509 lockscreen.clear(true);
510 if (greeter.narrowMode) {
511 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
512 }
513 }
514 }
515 }
517 Binding {
518 target: LightDM.Greeter
519 property: "active"
520 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
521 }
523 Rectangle {
524 anchors.fill: parent
525 color: "black"
526 opacity: greeterWrapper.showProgress * 0.8
527 }
529 Item {
530 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
531 id: greeterWrapper
532 objectName: "greeterWrapper"
533 x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
534 y: panel.panelHeight
535 width: parent.width
536 height: parent.height - panel.panelHeight
538 Behavior on x {
539 enabled: !launcher.dashSwipe
540 StandardAnimation {}
541 }
543 property bool fullyShown: showProgress === 1.0
544 onFullyShownChanged: {
545 // Wait until the greeter is completely covering lockscreen before resetting it.
546 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
547 lockscreen.reset();
548 lockscreen.maybeShow();
549 }
550 }
552 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
553 onShowProgressChanged: {
554 if (showProgress === 0) {
555 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
556 greeter.login()
557 } else if (greeter.narrowMode) {
558 lockscreen.clear(false) // to reset focus if necessary
559 }
560 }
561 }
563 Greeter {
564 id: greeter
565 objectName: "greeter"
567 signal sessionStarted() // helpful for tests
569 property string lockedApp: ""
570 property bool hasLockedApp: lockedApp !== ""
572 hides: [launcher, panel.indicators]
573 loadContent: required || lockscreen.required // keeps content in memory for quick show()
575 locked: shell.locked
577 background: shell.background
579 width: parent.width
580 height: parent.height
583 // avoid overlapping with Launcher's edge drag area
584 // FIXME: Fix TouchRegistry & friends and remove this workaround
585 // Issue involves launcher's DDA getting disabled on a long
586 // left-edge drag
587 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
589 function startUnlock() {
590 if (narrowMode) {
591 if (!LightDM.Greeter.authenticated) {
592 lockscreen.maybeShow()
593 }
594 hide()
595 } else {
596 show()
597 tryToUnlock()
598 }
599 }
601 function login() {
602 enabled = false;
603 if (LightDM.Greeter.startSessionSync()) {
604 sessionStarted();
605 greeter.hide();
606 lockscreen.hide();
607 launcher.hide();
608 }
609 enabled = true;
610 }
612 Timer {
613 // See powerConnection for why this is useful
614 id: showGreeterDelayed
615 interval: 1
616 onTriggered: {
617 greeter.showNow();
618 }
619 }
621 onShownChanged: {
622 if (shown) {
623 // Disable everything so that user can't swipe greeter or
624 // launcher until we get the next prompt/authenticate, which
625 // will re-enable the shell.
626 shell.enabled = false;
628 if (greeter.narrowMode) {
629 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
630 } else {
631 reset()
632 }
633 greeter.lockedApp = "";
634 greeter.forceActiveFocus();
635 }
636 }
638 Component.onCompleted: {
639 Connectivity.unlockAllModems()
640 }
642 onUnlocked: greeter.hide()
643 onSelected: {
644 // Update launcher items for new user
645 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
646 AccountsService.user = user;
647 LauncherModel.setUser(user);
648 }
650 onTapped: {
651 if (!tutorial.running) {
652 launcher.tease();
653 }
654 }
655 onDraggingChanged: {
656 if (dragging && !tutorial.running) {
657 launcher.tease();
658 }
659 }
661 Binding {
662 target: ApplicationManager
663 property: "suspended"
664 value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
665 }
666 }
667 }
669 Connections {
670 id: callConnection
671 target: callManager
673 onHasCallsChanged: {
674 if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
675 // We just received an incoming call while locked. The
676 // indicator will have already launched dialer-app for us, but
677 // there is a race between "hasCalls" changing and the dialer
678 // starting up. So in case we lose that race, we'll start/
679 // focus the dialer ourselves here too. Even if the indicator
680 // didn't launch the dialer for some reason (or maybe a call
681 // started via some other means), if an active call is
682 // happening, we want to be in the dialer.
683 startLockedApp("dialer-app")
684 }
685 }
686 }
688 Connections {
689 id: powerConnection
690 target: Powerd
692 onStatusChanged: {
693 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
694 !callManager.hasCalls && !tutorial.running) {
695 // We don't want to simply call greeter.showNow() here, because
696 // that will take too long. Qt will delay button event
697 // handling until the greeter is done loading and may think the
698 // user held down the power button the whole time, leading to a
699 // power dialog being shown. Instead, delay showing the
700 // greeter until we've finished handling the event. We could
701 // make the greeter load asynchronously instead, but that
702 // introduces a whole host of timing issues, especially with
703 // its animations. So this is simpler.
704 showGreeterDelayed.start();
705 }
706 }
707 }
709 function showHome() {
710 if (tutorial.running) {
711 return
712 }
714 if (LightDM.Greeter.active) {
715 greeter.startUnlock()
716 }
718 var animate = !LightDM.Greeter.active && !stages.shown
719 dash.setCurrentScope(0, animate, false)
720 ApplicationManager.requestFocusApplication("unity8-dash")
721 }
723 function showDash() {
724 if (greeter.hasLockedApp || // just in case user gets here
725 (!greeter.narrowMode && shell.locked)) {
726 return
727 }
729 if (greeter.shown) {
730 greeter.hideRight();
731 launcher.fadeOut();
732 }
734 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
735 ApplicationManager.requestFocusApplication("unity8-dash")
736 launcher.fadeOut();
737 }
738 }
740 Item {
741 id: overlay
742 z: 10
744 anchors.fill: parent
746 Panel {
747 id: panel
748 objectName: "panel"
749 anchors.fill: parent //because this draws indicator menus
750 indicators {
751 hides: [launcher]
752 available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
753 contentEnabled: tutorial.panelContentEnabled
754 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
756 minimizedPanelHeight: units.gu(3)
757 expandedPanelHeight: units.gu(7)
759 indicatorsModel: Indicators.IndicatorsModel {
760 Component.onCompleted: load(indicatorProfile);
761 }
762 }
763 callHint {
764 greeterShown: greeter.shown || lockscreen.shown
765 }
767 property bool topmostApplicationIsFullscreen:
768 ApplicationManager.focusedApplicationId &&
769 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
771 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
772 || greeter.hasLockedApp
773 }
775 Launcher {
776 id: launcher
777 objectName: "launcher"
779 readonly property bool dashSwipe: progress > 0
781 anchors.top: parent.top
782 anchors.topMargin: inverted ? 0 : panel.panelHeight
783 anchors.bottom: parent.bottom
784 width: parent.width
785 dragAreaWidth: shell.edgeSize
786 available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
787 inverted: usageModeSettings.usageMode === "Staged"
788 shadeBackground: !tutorial.running
790 onShowDashHome: showHome()
791 onDash: showDash()
792 onDashSwipeChanged: {
793 if (dashSwipe) {
794 dash.setCurrentScope(0, false, true)
795 }
796 }
797 onLauncherApplicationSelected: {
798 if (greeter.hasLockedApp) {
799 greeter.startUnlock()
800 }
801 if (!tutorial.running)
802 shell.activateApplication(appId)
803 }
804 onShownChanged: {
805 if (shown) {
806 panel.indicators.hide()
807 }
808 }
809 }
811 Wizard {
812 id: wizard
813 anchors.fill: parent
814 background: shell.background
815 }
817 Rectangle {
818 id: modalNotificationBackground
820 visible: notifications.useModal && (notifications.state == "narrow")
821 color: "#000000"
822 anchors.fill: parent
823 opacity: 0.9
825 MouseArea {
826 anchors.fill: parent
827 }
828 }
830 Notifications {
831 id: notifications
833 model: NotificationBackend.Model
834 margin: units.gu(1)
836 y: topmostIsFullscreen ? 0 : panel.panelHeight
837 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
839 states: [
840 State {
841 name: "narrow"
842 when: overlay.width <= units.gu(60)
843 AnchorChanges {
844 target: notifications
845 anchors.left: parent.left
846 anchors.right: parent.right
847 }
848 },
849 State {
850 name: "wide"
851 when: overlay.width > units.gu(60)
852 AnchorChanges {
853 target: notifications
854 anchors.left: undefined
855 anchors.right: parent.right
856 }
857 PropertyChanges { target: notifications; width: units.gu(38) }
858 }
859 ]
860 }
861 }
863 Dialogs {
864 id: dialogs
865 anchors.fill: parent
866 z: overlay.z + 10
867 onPowerOffClicked: {
868 shutdownFadeOutRectangle.enabled = true;
869 shutdownFadeOutRectangle.visible = true;
870 shutdownFadeOut.start();
871 }
872 }
874 Tutorial {
875 id: tutorial
876 objectName: "tutorial"
877 active: AccountsService.demoEdges
878 paused: LightDM.Greeter.active
879 launcher: launcher
880 panel: panel
881 stages: stages
882 overlay: overlay
884 onFinished: AccountsService.demoEdges = false
885 }
887 Connections {
888 target: SessionBroadcast
889 onShowHome: showHome()
890 }
892 Rectangle {
893 id: shutdownFadeOutRectangle
894 z: screenGrabber.z + 10
895 enabled: false
896 visible: false
897 color: "black"
898 anchors.fill: parent
899 opacity: 0.0
900 NumberAnimation on opacity {
901 id: shutdownFadeOut
902 from: 0.0
903 to: 1.0
904 onStopped: {
905 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
906 DBusUnitySessionService.Shutdown();
907 }
908 }
909 }
910 }