on 6ced7b1 help sign in
unity8/qml/Shell.qml
809 lines 29 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 AccountsService 0.1
19import GSettings 1.0
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Gestures 0.1
23import Unity.Launcher 0.1
24import LightDM 0.1 as LightDM
25import Powerd 0.1
26import SessionBroadcast 0.1
27import "Dash"
28import "Greeter"
29import "Launcher"
30import "Panel"
31import "Hud"
32import "Components"
33import "Bottombar"
34import "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38FocusScope {
39 id: shell
41 // this is only here to select the width / height of the window if not running fullscreen
42 property bool tablet: false
43 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
44 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
46 property real edgeSize: units.gu(2)
47 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
48 property url background
49 readonly property real panelHeight: panel.panelHeight
51 property bool dashShown: dash.shown
52 property bool stageScreenshotsReady: {
53 if (sideStage.shown) {
54 if (mainStage.applications.count > 0) {
55 return mainStage.usingScreenshots && sideStage.usingScreenshots;
56 } else {
57 return sideStage.usingScreenshots;
58 }
59 } else {
60 return mainStage.usingScreenshots;
61 }
62 }
64 property var applicationManager: ApplicationManagerWrapper {}
66 Binding {
67 target: LauncherModel
68 property: "applicationManager"
69 value: ApplicationManager
70 }
72 Component.onCompleted: {
73 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
77 // FIXME: if application focused before shell starts, shell draws on top of it only.
78 // We should detect already running applications on shell start and bring them to the front.
79 applicationManager.unfocusCurrentApplication();
80 }
82 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
83 || !!applicationManager.sideStageFocusedApplication
84 // Used for autopilot testing.
85 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
87 readonly property bool fullscreenMode: {
88 if (greeter.shown || lockscreen.shown) {
89 return false;
90 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
91 return mainStage.switchingFromFullscreenToFullscreen;
92 } else if (applicationManager.mainStageFocusedApplication) {
93 return applicationManager.mainStageFocusedApplication.fullscreen;
94 } else {
95 return false;
96 }
97 }
99 function activateApplication(appId, argument) {
100 if (applicationManager) {
101 // For newly started applications, as it takes them time to draw their first frame
102 // we add a delay before we hide the animation screenshots to compensate.
103 var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
105 var application;
106 application = applicationManager.activateApplication(appId, argument);
107 if (application == null) {
108 return;
109 }
110 edgeDemo.stopDemo();
111 greeter.hide();
112 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
113 mainStage.activateApplication(appId, addDelay);
114 } else {
115 sideStage.activateApplication(appId, addDelay);
116 }
117 stages.show();
118 }
119 }
121 GSettings {
122 id: backgroundSettings
123 schema.id: "org.gnome.desktop.background"
124 }
125 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
126 onGSettingsPictureChanged: {
127 shell.background = gSettingsPicture
128 }
130 // This is a dummy image that is needed to determine if the picture url
131 // in backgroundSettings points to a valid picture file.
132 // We can't do this with the real background image because setting a
133 // new source in onStatusChanged triggers a binding loop detection
134 // inside Image, which causes it not to render even though a valid source
135 // would be set. We don't mind about this image staying black and just
136 // use it for verification to populate the source for the real
137 // background image.
138 Image {
139 source: shell.background
140 height: 0
141 width: 0
142 sourceSize.height: 0
143 sourceSize.width: 0
144 onStatusChanged: {
145 if (status == Image.Error && source != shell.defaultBackground) {
146 shell.background = defaultBackground
147 }
148 }
149 }
151 VolumeControl {
152 id: volumeControl
153 }
155 Keys.onVolumeUpPressed: volumeControl.volumeUp()
156 Keys.onVolumeDownPressed: volumeControl.volumeDown()
158 Item {
159 id: underlay
160 objectName: "underlay"
161 anchors.fill: parent
163 // Whether the underlay is fully covered by opaque UI elements.
164 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
166 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
167 || (sideStage.applications && sideStage.applications.count > 0))
169 // Whether the user should see the topmost application surface (if there's one at all).
170 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
171 && !mainStage.usingScreenshots // but want sideStage animating over app surface
175 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
176 // through the translucent parts of the shell surface.
177 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
179 CrossFadeImage {
180 id: backgroundImage
181 objectName: "backgroundImage"
183 anchors.fill: parent
184 source: shell.background
185 fillMode: Image.PreserveAspectCrop
186 }
188 Rectangle {
189 anchors.fill: parent
190 color: "black"
191 opacity: dash.disappearingAnimationProgress
192 }
194 Dash {
195 id: dash
196 objectName: "dash"
198 available: !greeter.shown && !lockscreen.shown
199 hides: [stages, launcher, panel.indicators]
200 shown: disappearingAnimationProgress !== 1.0
201 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
202 // FIXME: unfocus all applications when going back to the dash
203 onEnabledChanged: {
204 if (enabled) {
205 shell.applicationManager.unfocusCurrentApplication()
206 }
207 }
209 anchors {
210 fill: parent
211 topMargin: panel.panelHeight
212 }
214 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
215 opacity: 1.0 - disappearingAnimationProgress
216 property real disappearingAnimationProgress: {
217 if (greeter.shown) {
218 return greeter.showProgress;
219 } else {
220 return stagesOuterContainer.showProgress;
221 }
222 }
224 // FIXME: only necessary because stagesOuterContainer.showProgress and
225 // greeterRevealer.animatedProgress are not animated
226 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
227 }
228 }
230 Item {
231 id: stagesOuterContainer
233 width: parent.width
234 height: parent.height
235 x: launcher.progress
236 Behavior on x {SmoothedAnimation{velocity: 600}}
238 property real showProgress:
239 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
241 Showable {
242 id: stages
243 objectName: "stages"
245 x: width
247 property bool fullyShown: shown && x == 0 && parent.x == 0
248 property bool fullyHidden: !shown && x == width
249 available: !greeter.shown
250 hides: [panel.indicators]
251 shown: false
252 opacity: 1.0
253 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
254 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
256 width: parent.width
257 height: parent.height
259 // close the stages when no focused application remains
260 Connections {
261 target: shell.applicationManager
262 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
263 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
264 ignoreUnknownSignals: true
265 }
267 function closeIfNoApplications() {
268 if (!shell.applicationManager.mainStageFocusedApplication
269 && !shell.applicationManager.sideStageFocusedApplication
270 && shell.applicationManager.mainStageApplications.count == 0
271 && shell.applicationManager.sideStageApplications.count == 0) {
272 stages.hide();
273 }
274 }
276 // show the stages when an application gets the focus
277 Connections {
278 target: shell.applicationManager
279 onMainStageFocusedApplicationChanged: {
280 if (shell.applicationManager.mainStageFocusedApplication) {
281 mainStage.show();
282 stages.show();
283 }
284 }
285 onSideStageFocusedApplicationChanged: {
286 if (shell.applicationManager.sideStageFocusedApplication) {
287 sideStage.show();
288 stages.show();
289 }
290 }
291 ignoreUnknownSignals: true
292 }
294 Stage {
295 id: mainStage
297 anchors.fill: parent
298 fullyShown: stages.fullyShown
299 fullyHidden: stages.fullyHidden
300 shouldUseScreenshots: !fullyShown
301 rightEdgeEnabled: !sideStage.enabled
303 applicationManager: shell.applicationManager
304 rightEdgeDraggingAreaWidth: shell.edgeSize
305 normalApplicationY: shell.panelHeight
307 shown: true
308 function show() {
309 stages.show();
310 }
311 function hide() {
312 }
314 // FIXME: workaround the fact that focusing a main stage application
315 // raises its surface on top of all other surfaces including the ones
316 // that belong to side stage applications.
317 onFocusedApplicationChanged: {
318 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
319 shell.applicationManager.focusApplication(sideStage.focusedApplication);
320 }
321 }
322 }
324 SideStage {
325 id: sideStage
327 applicationManager: shell.applicationManager
328 rightEdgeDraggingAreaWidth: shell.edgeSize
329 normalApplicationY: shell.panelHeight
331 onShownChanged: {
332 if (!shown && mainStage.applications.count == 0) {
333 stages.hide();
334 }
335 }
336 // FIXME: when hiding the side stage, refocus the main stage
337 // application so that it goes in front of the side stage
338 // application and hides it
339 onFullyShownChanged: {
340 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
341 shell.applicationManager.focusApplication(mainStage.focusedApplication);
342 }
343 }
345 enabled: shell.width >= units.gu(60)
346 visible: enabled
347 fullyShown: stages.fullyShown && shown
348 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
349 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
351 available: !greeter.shown && !lockscreen.shown && enabled
352 hides: [launcher, panel.indicators]
353 shown: false
354 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
355 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
357 width: units.gu(40)
358 height: stages.height
359 handleExpanded: sideStageRevealer.pressed
360 }
362 Revealer {
363 id: sideStageRevealer
365 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
366 && sideStage.available
367 direction: Qt.RightToLeft
368 openedValue: parent.width - sideStage.width
369 hintDisplacement: units.gu(3)
370 /* The size of the sidestage handle needs to be bigger than the
371 typical size used for edge detection otherwise it is really
372 hard to grab.
373 */
374 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
375 closedValue: parent.width + sideStage.handleSizeCollapsed
376 target: sideStage
377 x: parent.width - width
378 width: sideStage.width + handleSize * 0.7
379 height: sideStage.height
380 orientation: Qt.Horizontal
381 }
383 DragHandle {
384 id: stagesDragHandle
386 anchors.top: parent.top
387 anchors.bottom: parent.bottom
388 anchors.right: parent.left
390 width: shell.edgeSize
391 direction: Direction.Leftwards
392 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
393 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
395 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
396 // Make autocompletion impossible when !haveApps
397 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
398 }
399 }
400 }
402 Lockscreen {
403 id: lockscreen
404 objectName: "lockscreen"
406 readonly property int backgroundTopMargin: -panel.panelHeight
408 hides: [launcher, panel.indicators, hud]
409 shown: false
410 enabled: true
411 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
412 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
413 y: panel.panelHeight
414 x: required ? 0 : - width
415 width: parent.width
416 height: parent.height - panel.panelHeight
417 background: shell.background
418 pinLength: 4
420 onEntered: LightDM.Greeter.respond(passphrase);
421 onCancel: greeter.show()
423 Component.onCompleted: {
424 if (LightDM.Users.count == 1) {
425 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
426 greeter.selected(0)
427 }
428 }
429 }
431 Connections {
432 target: LightDM.Greeter
434 onShowPrompt: {
435 if (LightDM.Users.count == 1) {
436 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
437 if (text == "PIN") {
438 lockscreen.alphaNumeric = false
439 } else {
440 lockscreen.alphaNumeric = true
441 }
442 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
443 lockscreen.show();
444 }
445 }
447 onAuthenticationComplete: {
448 if (LightDM.Greeter.promptless) {
449 return;
450 }
451 if (LightDM.Greeter.authenticated) {
452 lockscreen.hide();
453 } else {
454 lockscreen.clear(true);
455 }
456 }
457 }
459 Greeter {
460 id: greeter
461 objectName: "greeter"
463 available: true
464 hides: [launcher, panel.indicators, hud]
465 shown: true
467 defaultBackground: shell.background
469 y: panel.panelHeight
470 width: parent.width
471 height: parent.height - panel.panelHeight
473 dragHandleWidth: shell.edgeSize
475 property var previousMainApp: null
476 property var previousSideApp: null
478 function removeApplicationFocus() {
479 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
480 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
481 applicationManager.unfocusCurrentApplication();
482 }
484 function restoreApplicationFocus() {
485 if (greeter.previousMainApp) {
486 applicationManager.focusApplication(greeter.previousMainApp);
487 greeter.previousMainApp = null;
488 }
489 if (greeter.previousSideApp) {
490 applicationManager.focusApplication(greeter.previousSideApp);
491 greeter.previousSideApp = null;
492 }
493 }
495 onShownChanged: {
496 if (shown) {
497 lockscreen.reset();
498 // If there is only one user, we start authenticating with that one here.
499 // If there are more users, the Greeter will handle that
500 if (LightDM.Users.count == 1) {
501 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
502 greeter.selected(0);
503 }
504 greeter.forceActiveFocus();
505 removeApplicationFocus();
506 } else {
507 restoreApplicationFocus();
508 }
509 }
511 onUnlocked: greeter.hide()
512 onSelected: {
513 // Update launcher items for new user
514 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
515 AccountsService.user = user;
516 LauncherModel.setUser(user);
517 }
519 onLeftTeaserPressedChanged: {
520 if (leftTeaserPressed) {
521 launcher.tease();
522 }
523 }
525 Connections {
526 target: applicationManager
527 ignoreUnknownSignals: true
528 // If any app is focused when greeter is open, it's due to a user action
529 // like a snap decision (say, an incoming call).
530 // TODO: these should be protected to only unlock for certain applications / certain usecases
531 // potentially only in connection with a notification.
532 onMainStageFocusedApplicationChanged: {
533 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
534 greeter.previousMainApp = null // make way for new focused app
535 greeter.previousSideApp = null
536 greeter.hide()
537 }
538 }
539 onSideStageFocusedApplicationChanged: {
540 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
541 greeter.previousMainApp = null // make way for new focused app
542 greeter.previousSideApp = null
543 greeter.hide()
544 }
545 }
546 onFocusRequested: shell.activateApplication(appId)
547 }
548 }
550 InputFilterArea {
551 anchors.fill: parent
552 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
553 || panel.indicators.shown || hud.shown
554 }
556 Connections {
557 id: powerConnection
558 target: Powerd
560 onDisplayPowerStateChange: {
561 // We ignore any display-off signals when the proximity sensor
562 // is active. This usually indicates something like a phone call.
563 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
564 greeter.showNow();
565 }
567 // No reason to chew demo CPU when user isn't watching
568 if (status == Powerd.Off) {
569 edgeDemo.paused = true;
570 } else if (status == Powerd.On) {
571 edgeDemo.paused = false;
572 }
573 }
574 }
576 function showHome() {
577 var animate = !greeter.shown && !stages.shown
578 greeter.hide()
579 dash.setCurrentScope("home.scope", animate, false)
580 stages.hide()
581 }
583 function hideIndicatorMenu(delay) {
584 panel.hideIndicatorMenu(delay);
585 }
587 Item {
588 id: overlay
590 anchors.fill: parent
592 Panel {
593 id: panel
594 anchors.fill: parent //because this draws indicator menus
595 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
596 indicators {
597 hides: [launcher]
598 available: edgeDemo.panelEnabled
599 contentEnabled: edgeDemo.panelContentEnabled
600 }
601 fullscreenMode: shell.fullscreenMode
602 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
604 InputFilterArea {
605 anchors {
606 top: parent.top
607 left: parent.left
608 right: parent.right
609 }
610 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
611 blockInput: true
612 }
613 }
615 Hud {
616 id: hud
618 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
619 height: parent.height
621 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
622 shown: false
623 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
624 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
626 Connections {
627 target: shell.applicationManager
628 onMainStageFocusedApplicationChanged: hud.hide()
629 onSideStageFocusedApplicationChanged: hud.hide()
630 }
631 }
633 Revealer {
634 id: hudRevealer
636 enabled: hud.shown
637 width: hud.width
638 anchors.left: hud.left
639 height: parent.height
640 target: hud.revealerTarget
641 closedValue: height
642 openedValue: 0
643 direction: Qt.RightToLeft
644 orientation: Qt.Vertical
645 handleSize: hud.handleHeight
646 onCloseClicked: target.hide()
647 }
649 Bottombar {
650 id: bottombar
651 theHud: hud
652 anchors.fill: parent
653 enabled: hud.available
654 applicationIsOnForeground: applicationFocused
655 }
657 InputFilterArea {
658 blockInput: launcher.shown
659 anchors {
660 top: parent.top
661 bottom: parent.bottom
662 left: parent.left
663 }
664 width: launcher.width
665 }
667 Launcher {
668 id: launcher
670 readonly property bool dashSwipe: progress > 0
672 anchors.top: parent.top
673 anchors.bottom: parent.bottom
674 width: parent.width
675 dragAreaWidth: shell.edgeSize
676 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
678 onShowDashHome: {
679 if (edgeDemo.running)
680 return;
682 showHome()
683 }
684 onDash: {
685 if (stages.shown) {
686 stages.hide();
687 launcher.hide();
688 }
689 }
690 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
691 onLauncherApplicationSelected: {
692 if (!edgeDemo.running)
693 shell.activateApplication(appId)
694 }
695 onShownChanged: {
696 if (shown) {
697 panel.indicators.hide()
698 hud.hide()
699 bottombar.hide()
700 }
701 }
702 }
704 Notifications {
705 id: notifications
707 model: NotificationBackend.Model
708 margin: units.gu(1)
710 anchors {
711 top: parent.top
712 right: parent.right
713 bottom: parent.bottom
714 topMargin: panel.panelHeight
715 }
716 states: [
717 State {
718 name: "narrow"
719 when: overlay.width <= units.gu(60)
720 AnchorChanges { target: notifications; anchors.left: parent.left }
721 },
722 State {
723 name: "wide"
724 when: overlay.width > units.gu(60)
725 AnchorChanges { target: notifications; anchors.left: undefined }
726 PropertyChanges { target: notifications; width: units.gu(38) }
727 }
728 ]
730 InputFilterArea {
731 anchors { left: parent.left; right: parent.right }
732 height: parent.contentHeight
733 blockInput: height > 0
734 }
735 }
736 }
738 focus: true
739 onFocusChanged: if (!focus) forceActiveFocus();
741 InputFilterArea {
742 anchors {
743 top: parent.top
744 bottom: parent.bottom
745 left: parent.left
746 }
747 width: shell.edgeSize
748 blockInput: true
749 }
751 InputFilterArea {
752 anchors {
753 top: parent.top
754 bottom: parent.bottom
755 right: parent.right
756 }
757 width: shell.edgeSize
758 blockInput: true
759 }
761 Binding {
762 target: i18n
763 property: "domain"
764 value: "unity8"
765 }
767 OSKController {
768 anchors.topMargin: panel.panelHeight
769 anchors.fill: parent // as needs to know the geometry of the shell
770 }
772 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
773 MouseArea {
774 anchors.bottom: parent.bottom
775 anchors.left: parent.left
776 anchors.right: parent.right
777 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
779 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
780 }
782 Label {
783 anchors.centerIn: parent
784 visible: applicationManager.fake
785 text: "EARLY ALPHA\nNOT READY FOR USE"
786 color: "lightgrey"
787 opacity: 0.2
788 font.weight: Font.Black
789 horizontalAlignment: Text.AlignHCenter
790 verticalAlignment: Text.AlignVCenter
791 fontSizeMode: Text.Fit
792 rotation: -45
793 scale: Math.min(parent.width, parent.height) / width
794 }
796 EdgeDemo {
797 id: edgeDemo
798 greeter: greeter
799 launcher: launcher
800 dash: dash
801 indicators: panel.indicators
802 underlay: underlay
803 }
805 Connections {
806 target: SessionBroadcast
807 onShowHome: showHome()
808 }