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