on 6bd9cff help sign in
unity8/qml/Shell.qml
927 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 property bool interactive: tutorial.spreadEnabled
294 && !greeter.shown
295 && !lockscreen.shown
296 && panel.indicators.fullyClosed
297 && launcher.progress == 0
298 && !notifications.useModal
300 onInteractiveChanged: { if (interactive) { focus = true; } }
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 }
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 }
353 Connections {
354 target: SurfaceManager
355 onSurfaceCreated: {
356 if (surface.type == MirSurfaceItem.InputMethod) {
357 inputMethod.surface = surface;
358 }
359 }
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 }
382 Lockscreen {
383 id: lockscreen
384 objectName: "lockscreen"
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
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")
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
419 onEntered: LightDM.Greeter.respond(passphrase);
420 onCancel: greeter.show()
421 onEmergencyCall: startLockedApp("dialer-app")
423 onShownChanged: if (shown) greeter.lockedApp = ""
425 function maybeShow() {
426 if (!shell.forcedUnlock && !shown) {
427 showNow();
428 }
429 }
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 }
444 Component.onCompleted: {
445 if (greeter.narrowMode) {
446 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447 }
448 }
449 }
451 Connections {
452 target: LightDM.Greeter
454 onShowGreeter: greeter.show()
455 onHideGreeter: greeter.login()
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 }
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 }
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.
491 if (LightDM.Greeter.promptless) {
492 return;
493 }
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 }
518 lockscreen.clear(true);
519 if (greeter.narrowMode) {
520 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
521 }
522 }
523 }
524 }
526 Binding {
527 target: LightDM.Greeter
528 property: "active"
529 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
530 }
532 Rectangle {
533 anchors.fill: parent
534 color: "black"
535 opacity: greeterWrapper.showProgress * 0.8
536 }
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
547 Behavior on x {
548 enabled: !launcher.dashSwipe
549 StandardAnimation {}
550 }
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 }
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 }
572 Greeter {
573 id: greeter
574 objectName: "greeter"
576 signal sessionStarted() // helpful for tests
578 property string lockedApp: ""
579 property bool hasLockedApp: lockedApp !== ""
581 hides: [launcher, panel.indicators]
582 loadContent: required || lockscreen.required // keeps content in memory for quick show()
584 locked: shell.locked
586 background: shell.background
588 width: parent.width
589 height: parent.height
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
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 }
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 }
621 Timer {
622 // See powerConnection for why this is useful
623 id: showGreeterDelayed
624 interval: 1
625 onTriggered: {
626 greeter.showNow();
627 }
628 }
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;
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 }
647 Component.onCompleted: {
648 Connectivity.unlockAllModems()
649 }
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 }
659 onTapped: {
660 if (!tutorial.running) {
661 launcher.tease();
662 }
663 }
664 onDraggingChanged: {
665 if (dragging && !tutorial.running) {
666 launcher.tease();
667 }
668 }
670 Binding {
671 target: ApplicationManager
672 property: "suspended"
673 value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
674 }
675 }
676 }
678 Connections {
679 id: callConnection
680 target: callManager
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 }
697 Connections {
698 id: powerConnection
699 target: Powerd
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 }
718 function showHome() {
719 if (tutorial.running) {
720 return
721 }
723 if (LightDM.Greeter.active) {
724 greeter.startUnlock()
725 }
727 var animate = !LightDM.Greeter.active && !stages.shown
728 dash.setCurrentScope(0, animate, false)
729 ApplicationManager.requestFocusApplication("unity8-dash")
730 }
732 function showDash() {
733 if (greeter.hasLockedApp || // just in case user gets here
734 (!greeter.narrowMode && shell.locked)) {
735 return
736 }
738 if (greeter.shown) {
739 greeter.hideRight();
740 launcher.fadeOut();
741 }
743 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
744 ApplicationManager.requestFocusApplication("unity8-dash")
745 launcher.fadeOut();
746 }
747 }
749 Item {
750 id: overlay
751 z: 10
753 anchors.fill: parent
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
765 minimizedPanelHeight: units.gu(3)
766 expandedPanelHeight: units.gu(7)
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 }
778 property bool topmostApplicationIsFullscreen:
779 ApplicationManager.focusedApplicationId &&
780 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
782 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
783 || greeter.hasLockedApp
784 }
786 Launcher {
787 id: launcher
788 objectName: "launcher"
790 readonly property bool dashSwipe: progress > 0
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
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 }
822 Wizard {
823 id: wizard
824 anchors.fill: parent
825 background: shell.background
826 }
828 Rectangle {
829 id: modalNotificationBackground
831 visible: notifications.useModal && (notifications.state == "narrow")
832 color: "#000000"
833 anchors.fill: parent
834 opacity: 0.9
836 MouseArea {
837 anchors.fill: parent
838 }
839 }
841 Notifications {
842 id: notifications
844 model: NotificationBackend.Model
845 margin: units.gu(1)
847 y: topmostIsFullscreen ? 0 : panel.panelHeight
848 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
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 }
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 }
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
896 onFinished: {
897 AccountsService.demoEdges = false;
898 active = false; // for immediate response / if AS is having problems
899 }
900 }
902 Connections {
903 target: SessionBroadcast
904 onShowHome: showHome()
905 }
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 }