on c836a48 help sign in
unity8/qml/Shell.qml
785 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 "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 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
111 mainStage.activateApplication(appId, addDelay);
112 } else {
113 sideStage.activateApplication(appId, addDelay);
114 }
115 stages.show();
116 }
117 }
119 GSettings {
120 id: backgroundSettings
121 schema.id: "org.gnome.desktop.background"
122 }
123 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
124 onGSettingsPictureChanged: {
125 shell.background = gSettingsPicture
126 }
128 VolumeControl {
129 id: volumeControl
130 }
132 Keys.onVolumeUpPressed: volumeControl.volumeUp()
133 Keys.onVolumeDownPressed: volumeControl.volumeDown()
135 Item {
136 id: underlay
137 objectName: "underlay"
138 anchors.fill: parent
140 // Whether the underlay is fully covered by opaque UI elements.
141 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
143 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
144 || (sideStage.applications && sideStage.applications.count > 0))
146 // Whether the user should see the topmost application surface (if there's one at all).
147 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
148 && !mainStage.usingScreenshots // but want sideStage animating over app surface
152 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
153 // through the translucent parts of the shell surface.
154 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
156 Rectangle {
157 anchors.fill: parent
158 color: "black"
159 opacity: dash.disappearingAnimationProgress
160 }
162 Image {
163 anchors.fill: dash
164 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
165 fillMode: Image.PreserveAspectCrop
166 horizontalAlignment: Image.AlignRight
167 verticalAlignment: Image.AlignTop
168 }
170 Dash {
171 id: dash
172 objectName: "dash"
174 available: !greeter.shown && !lockscreen.shown
175 hides: [stages, launcher, panel.indicators]
176 shown: disappearingAnimationProgress !== 1.0
177 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
178 // FIXME: unfocus all applications when going back to the dash
179 onEnabledChanged: {
180 if (enabled) {
181 shell.applicationManager.unfocusCurrentApplication()
182 }
183 }
185 anchors {
186 fill: parent
187 topMargin: panel.panelHeight
188 }
190 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
191 opacity: 1.0 - disappearingAnimationProgress
192 property real disappearingAnimationProgress: {
193 if (greeter.shown) {
194 return greeter.showProgress;
195 } else {
196 return stagesOuterContainer.showProgress;
197 }
198 }
200 // FIXME: only necessary because stagesOuterContainer.showProgress and
201 // greeterRevealer.animatedProgress are not animated
202 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
203 }
204 }
206 Item {
207 id: stagesOuterContainer
209 width: parent.width
210 height: parent.height
211 x: launcher.progress
212 Behavior on x {SmoothedAnimation{velocity: 600}}
214 property real showProgress:
215 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
217 Showable {
218 id: stages
219 objectName: "stages"
221 x: width
223 property bool fullyShown: shown && x == 0 && parent.x == 0
224 property bool fullyHidden: !shown && x == width
225 available: !greeter.shown
226 hides: [panel.indicators]
227 shown: false
228 opacity: 1.0
229 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
230 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
232 width: parent.width
233 height: parent.height
235 // close the stages when no focused application remains
236 Connections {
237 target: shell.applicationManager
238 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
239 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
240 ignoreUnknownSignals: true
241 }
243 function closeIfNoApplications() {
244 if (!shell.applicationManager.mainStageFocusedApplication
245 && !shell.applicationManager.sideStageFocusedApplication
246 && shell.applicationManager.mainStageApplications.count == 0
247 && shell.applicationManager.sideStageApplications.count == 0) {
248 stages.hide();
249 }
250 }
252 // show the stages when an application gets the focus
253 Connections {
254 target: shell.applicationManager
255 onMainStageFocusedApplicationChanged: {
256 if (shell.applicationManager.mainStageFocusedApplication) {
257 mainStage.show();
258 stages.show();
259 }
260 }
261 onSideStageFocusedApplicationChanged: {
262 if (shell.applicationManager.sideStageFocusedApplication) {
263 sideStage.show();
264 stages.show();
265 }
266 }
267 ignoreUnknownSignals: true
268 }
270 Stage {
271 id: mainStage
273 anchors.fill: parent
274 fullyShown: stages.fullyShown
275 fullyHidden: stages.fullyHidden
276 shouldUseScreenshots: !fullyShown
277 rightEdgeEnabled: !sideStage.enabled
279 applicationManager: shell.applicationManager
280 rightEdgeDraggingAreaWidth: shell.edgeSize
281 normalApplicationY: shell.panelHeight
283 shown: true
284 function show() {
285 stages.show();
286 }
287 function hide() {
288 }
290 // FIXME: workaround the fact that focusing a main stage application
291 // raises its surface on top of all other surfaces including the ones
292 // that belong to side stage applications.
293 onFocusedApplicationChanged: {
294 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
295 shell.applicationManager.focusApplication(sideStage.focusedApplication);
296 }
297 }
298 }
300 SideStage {
301 id: sideStage
303 applicationManager: shell.applicationManager
304 rightEdgeDraggingAreaWidth: shell.edgeSize
305 normalApplicationY: shell.panelHeight
307 onShownChanged: {
308 if (!shown && mainStage.applications.count == 0) {
309 stages.hide();
310 }
311 }
312 // FIXME: when hiding the side stage, refocus the main stage
313 // application so that it goes in front of the side stage
314 // application and hides it
315 onFullyShownChanged: {
316 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
317 shell.applicationManager.focusApplication(mainStage.focusedApplication);
318 }
319 }
321 enabled: shell.width >= units.gu(100)
322 visible: enabled
323 fullyShown: stages.fullyShown && shown
324 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
325 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
327 available: !greeter.shown && !lockscreen.shown && enabled
328 hides: [launcher, panel.indicators]
329 shown: false
330 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
331 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
333 width: units.gu(40)
334 height: stages.height
335 handleExpanded: sideStageRevealer.pressed
336 }
338 Revealer {
339 id: sideStageRevealer
341 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
342 && sideStage.available
343 direction: Qt.RightToLeft
344 openedValue: parent.width - sideStage.width
345 hintDisplacement: units.gu(3)
346 /* The size of the sidestage handle needs to be bigger than the
347 typical size used for edge detection otherwise it is really
348 hard to grab.
349 */
350 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
351 closedValue: parent.width + sideStage.handleSizeCollapsed
352 target: sideStage
353 x: parent.width - width
354 width: sideStage.width + handleSize * 0.7
355 height: sideStage.height
356 orientation: Qt.Horizontal
357 }
359 DragHandle {
360 id: stagesDragHandle
362 anchors.top: parent.top
363 anchors.bottom: parent.bottom
364 anchors.right: parent.left
366 width: shell.edgeSize
367 direction: Direction.Leftwards
368 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
369 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
371 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
372 // Make autocompletion impossible when !haveApps
373 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
374 }
375 }
376 }
378 Lockscreen {
379 id: lockscreen
380 objectName: "lockscreen"
382 readonly property int backgroundTopMargin: -panel.panelHeight
384 hides: [launcher, panel.indicators, hud]
385 shown: false
386 enabled: true
387 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
388 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
389 y: panel.panelHeight
390 x: required ? 0 : - width
391 width: parent.width
392 height: parent.height - panel.panelHeight
393 background: shell.background
394 pinLength: 4
396 onEntered: LightDM.Greeter.respond(passphrase);
397 onCancel: greeter.show()
399 Component.onCompleted: {
400 if (LightDM.Users.count == 1) {
401 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
402 }
403 }
404 }
406 Connections {
407 target: LightDM.Greeter
409 onShowPrompt: {
410 if (LightDM.Users.count == 1) {
411 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
412 if (text == "PIN") {
413 lockscreen.alphaNumeric = false
414 } else {
415 lockscreen.alphaNumeric = true
416 }
417 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
418 lockscreen.show();
419 }
420 }
422 onAuthenticationComplete: {
423 if (LightDM.Greeter.promptless) {
424 return;
425 }
426 if (LightDM.Greeter.authenticated) {
427 lockscreen.hide();
428 } else {
429 lockscreen.clear(true);
430 }
431 }
432 }
434 Greeter {
435 id: greeter
436 objectName: "greeter"
438 available: true
439 hides: [launcher, panel.indicators, hud]
440 shown: true
442 defaultBackground: shell.defaultBackground
444 y: panel.panelHeight
445 width: parent.width
446 height: parent.height - panel.panelHeight
448 dragHandleWidth: shell.edgeSize
450 property var previousMainApp: null
451 property var previousSideApp: null
453 function removeApplicationFocus() {
454 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
455 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
456 applicationManager.unfocusCurrentApplication();
457 }
459 function restoreApplicationFocus() {
460 if (greeter.previousMainApp) {
461 applicationManager.focusApplication(greeter.previousMainApp);
462 greeter.previousMainApp = null;
463 }
464 if (greeter.previousSideApp) {
465 applicationManager.focusApplication(greeter.previousSideApp);
466 greeter.previousSideApp = null;
467 }
468 }
470 onShownChanged: {
471 if (shown) {
472 lockscreen.reset();
473 // If there is only one user, we start authenticating with that one here.
474 // If there are more users, the Greeter will handle that
475 if (LightDM.Users.count == 1) {
476 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
477 }
478 greeter.forceActiveFocus();
479 removeApplicationFocus();
480 } else {
481 restoreApplicationFocus();
482 }
483 }
485 onUnlocked: greeter.hide()
486 onSelected: {
487 // Update launcher items for new user
488 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
489 AccountsService.user = user;
490 LauncherModel.setUser(user);
491 }
493 onLeftTeaserPressedChanged: {
494 if (leftTeaserPressed) {
495 launcher.tease();
496 }
497 }
499 Connections {
500 target: applicationManager
501 ignoreUnknownSignals: true
502 // If any app is focused when greeter is open, it's due to a user action
503 // like a snap decision (say, an incoming call).
504 // TODO: these should be protected to only unlock for certain applications / certain usecases
505 // potentially only in connection with a notification.
506 onMainStageFocusedApplicationChanged: {
507 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
508 greeter.previousMainApp = null // make way for new focused app
509 greeter.previousSideApp = null
510 greeter.hide()
511 }
512 }
513 onSideStageFocusedApplicationChanged: {
514 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
515 greeter.previousMainApp = null // make way for new focused app
516 greeter.previousSideApp = null
517 greeter.hide()
518 }
519 }
520 }
521 }
523 InputFilterArea {
524 anchors.fill: parent
525 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
526 || panel.indicators.shown || hud.shown
527 }
529 Connections {
530 id: powerConnection
531 target: Powerd
533 onDisplayPowerStateChange: {
534 // We ignore any display-off signals when the proximity sensor
535 // is active. This usually indicates something like a phone call.
536 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
537 greeter.showNow();
538 }
540 // No reason to chew demo CPU when user isn't watching
541 if (status == Powerd.Off) {
542 edgeDemo.paused = true;
543 } else if (status == Powerd.On) {
544 edgeDemo.paused = false;
545 }
546 }
547 }
549 function showHome() {
550 var animate = !greeter.shown && !stages.shown
551 greeter.hide()
552 dash.setCurrentScope("clickscope", animate, false)
553 stages.hide()
554 }
556 function hideIndicatorMenu(delay) {
557 panel.hideIndicatorMenu(delay);
558 }
560 Item {
561 id: overlay
563 anchors.fill: parent
565 Panel {
566 id: panel
567 anchors.fill: parent //because this draws indicator menus
568 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
569 indicators {
570 hides: [launcher]
571 available: edgeDemo.panelEnabled
572 contentEnabled: edgeDemo.panelContentEnabled
573 }
574 fullscreenMode: shell.fullscreenMode
575 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
577 InputFilterArea {
578 anchors {
579 top: parent.top
580 left: parent.left
581 right: parent.right
582 }
583 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
584 blockInput: true
585 }
586 }
588 Hud {
589 id: hud
591 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
592 height: parent.height
594 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
595 shown: false
596 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
597 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
599 Connections {
600 target: shell.applicationManager
601 onMainStageFocusedApplicationChanged: hud.hide()
602 onSideStageFocusedApplicationChanged: hud.hide()
603 }
604 }
606 Revealer {
607 id: hudRevealer
609 enabled: hud.shown
610 width: hud.width
611 anchors.left: hud.left
612 height: parent.height
613 target: hud.revealerTarget
614 closedValue: height
615 openedValue: 0
616 direction: Qt.RightToLeft
617 orientation: Qt.Vertical
618 handleSize: hud.handleHeight
619 onCloseClicked: target.hide()
620 }
622 Bottombar {
623 id: bottombar
624 theHud: hud
625 anchors.fill: parent
626 enabled: hud.available
627 applicationIsOnForeground: applicationFocused
628 }
630 InputFilterArea {
631 blockInput: launcher.shown
632 anchors {
633 top: parent.top
634 bottom: parent.bottom
635 left: parent.left
636 }
637 width: launcher.width
638 }
640 Launcher {
641 id: launcher
643 readonly property bool dashSwipe: progress > 0
645 anchors.top: parent.top
646 anchors.bottom: parent.bottom
647 width: parent.width
648 dragAreaWidth: shell.edgeSize
649 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
651 onShowDashHome: {
652 if (edgeDemo.running)
653 return;
655 showHome()
656 }
657 onDash: {
658 if (stages.shown) {
659 stages.hide();
660 launcher.hide();
661 }
662 }
663 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
664 onLauncherApplicationSelected:{
665 if (edgeDemo.running)
666 return;
668 greeter.hide()
669 shell.activateApplication(appId)
670 }
671 onShownChanged: {
672 if (shown) {
673 panel.indicators.hide()
674 hud.hide()
675 bottombar.hide()
676 }
677 }
678 }
680 Notifications {
681 id: notifications
683 model: NotificationBackend.Model
684 margin: units.gu(1)
686 anchors {
687 top: parent.top
688 right: parent.right
689 bottom: parent.bottom
690 topMargin: panel.panelHeight
691 }
692 states: [
693 State {
694 name: "narrow"
695 when: overlay.width <= units.gu(60)
696 AnchorChanges { target: notifications; anchors.left: parent.left }
697 },
698 State {
699 name: "wide"
700 when: overlay.width > units.gu(60)
701 AnchorChanges { target: notifications; anchors.left: undefined }
702 PropertyChanges { target: notifications; width: units.gu(38) }
703 }
704 ]
706 InputFilterArea {
707 anchors { left: parent.left; right: parent.right }
708 height: parent.contentHeight
709 blockInput: height > 0
710 }
711 }
712 }
714 focus: true
715 onFocusChanged: if (!focus) forceActiveFocus();
717 InputFilterArea {
718 anchors {
719 top: parent.top
720 bottom: parent.bottom
721 left: parent.left
722 }
723 width: shell.edgeSize
724 blockInput: true
725 }
727 InputFilterArea {
728 anchors {
729 top: parent.top
730 bottom: parent.bottom
731 right: parent.right
732 }
733 width: shell.edgeSize
734 blockInput: true
735 }
737 Binding {
738 target: i18n
739 property: "domain"
740 value: "unity8"
741 }
743 OSKController {
744 anchors.topMargin: panel.panelHeight
745 anchors.fill: parent // as needs to know the geometry of the shell
746 }
748 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
749 MouseArea {
750 anchors.bottom: parent.bottom
751 anchors.left: parent.left
752 anchors.right: parent.right
753 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
755 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
756 }
758 Label {
759 anchors.centerIn: parent
760 visible: applicationManager.fake
761 text: "EARLY ALPHA\nNOT READY FOR USE"
762 color: "lightgrey"
763 opacity: 0.2
764 font.weight: Font.Black
765 horizontalAlignment: Text.AlignHCenter
766 verticalAlignment: Text.AlignVCenter
767 fontSizeMode: Text.Fit
768 rotation: -45
769 scale: Math.min(parent.width, parent.height) / width
770 }
772 EdgeDemo {
773 id: edgeDemo
774 greeter: greeter
775 launcher: launcher
776 dash: dash
777 indicators: panel.indicators
778 underlay: underlay
779 }
781 Connections {
782 target: SessionBroadcast
783 onShowHome: showHome()
784 }