on 42e7231 help sign in
unity8/qml/Shell.qml
779 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 Dash {
163 id: dash
164 objectName: "dash"
166 available: !greeter.shown && !lockscreen.shown
167 hides: [stages, launcher, panel.indicators]
168 shown: disappearingAnimationProgress !== 1.0
169 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
170 // FIXME: unfocus all applications when going back to the dash
171 onEnabledChanged: {
172 if (enabled) {
173 shell.applicationManager.unfocusCurrentApplication()
174 }
175 }
177 anchors {
178 fill: parent
179 topMargin: panel.panelHeight
180 }
182 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
183 opacity: 1.0 - disappearingAnimationProgress
184 property real disappearingAnimationProgress: {
185 if (greeter.shown) {
186 return greeter.showProgress;
187 } else {
188 return stagesOuterContainer.showProgress;
189 }
190 }
192 // FIXME: only necessary because stagesOuterContainer.showProgress and
193 // greeterRevealer.animatedProgress are not animated
194 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
195 }
196 }
198 Item {
199 id: stagesOuterContainer
201 width: parent.width
202 height: parent.height
203 x: launcher.progress
204 Behavior on x {SmoothedAnimation{velocity: 600}}
206 property real showProgress:
207 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
209 Showable {
210 id: stages
211 objectName: "stages"
213 x: width
215 property bool fullyShown: shown && x == 0 && parent.x == 0
216 property bool fullyHidden: !shown && x == width
217 available: !greeter.shown
218 hides: [panel.indicators]
219 shown: false
220 opacity: 1.0
221 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
222 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
224 width: parent.width
225 height: parent.height
227 // close the stages when no focused application remains
228 Connections {
229 target: shell.applicationManager
230 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
231 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
232 ignoreUnknownSignals: true
233 }
235 function closeIfNoApplications() {
236 if (!shell.applicationManager.mainStageFocusedApplication
237 && !shell.applicationManager.sideStageFocusedApplication
238 && shell.applicationManager.mainStageApplications.count == 0
239 && shell.applicationManager.sideStageApplications.count == 0) {
240 stages.hide();
241 }
242 }
244 // show the stages when an application gets the focus
245 Connections {
246 target: shell.applicationManager
247 onMainStageFocusedApplicationChanged: {
248 if (shell.applicationManager.mainStageFocusedApplication) {
249 mainStage.show();
250 stages.show();
251 }
252 }
253 onSideStageFocusedApplicationChanged: {
254 if (shell.applicationManager.sideStageFocusedApplication) {
255 sideStage.show();
256 stages.show();
257 }
258 }
259 ignoreUnknownSignals: true
260 }
262 Stage {
263 id: mainStage
265 anchors.fill: parent
266 fullyShown: stages.fullyShown
267 fullyHidden: stages.fullyHidden
268 shouldUseScreenshots: !fullyShown
269 rightEdgeEnabled: !sideStage.enabled
271 applicationManager: shell.applicationManager
272 rightEdgeDraggingAreaWidth: shell.edgeSize
273 normalApplicationY: shell.panelHeight
275 shown: true
276 function show() {
277 stages.show();
278 }
279 function hide() {
280 }
282 // FIXME: workaround the fact that focusing a main stage application
283 // raises its surface on top of all other surfaces including the ones
284 // that belong to side stage applications.
285 onFocusedApplicationChanged: {
286 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
287 shell.applicationManager.focusApplication(sideStage.focusedApplication);
288 }
289 }
290 }
292 SideStage {
293 id: sideStage
295 applicationManager: shell.applicationManager
296 rightEdgeDraggingAreaWidth: shell.edgeSize
297 normalApplicationY: shell.panelHeight
299 onShownChanged: {
300 if (!shown && mainStage.applications.count == 0) {
301 stages.hide();
302 }
303 }
304 // FIXME: when hiding the side stage, refocus the main stage
305 // application so that it goes in front of the side stage
306 // application and hides it
307 onFullyShownChanged: {
308 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
309 shell.applicationManager.focusApplication(mainStage.focusedApplication);
310 }
311 }
313 enabled: shell.width >= units.gu(60)
314 visible: enabled
315 fullyShown: stages.fullyShown && shown
316 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
317 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
319 available: !greeter.shown && !lockscreen.shown && enabled
320 hides: [launcher, panel.indicators]
321 shown: false
322 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
323 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
325 width: units.gu(40)
326 height: stages.height
327 handleExpanded: sideStageRevealer.pressed
328 }
330 Revealer {
331 id: sideStageRevealer
333 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
334 && sideStage.available
335 direction: Qt.RightToLeft
336 openedValue: parent.width - sideStage.width
337 hintDisplacement: units.gu(3)
338 /* The size of the sidestage handle needs to be bigger than the
339 typical size used for edge detection otherwise it is really
340 hard to grab.
341 */
342 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
343 closedValue: parent.width + sideStage.handleSizeCollapsed
344 target: sideStage
345 x: parent.width - width
346 width: sideStage.width + handleSize * 0.7
347 height: sideStage.height
348 orientation: Qt.Horizontal
349 }
351 DragHandle {
352 id: stagesDragHandle
354 anchors.top: parent.top
355 anchors.bottom: parent.bottom
356 anchors.right: parent.left
358 width: shell.edgeSize
359 direction: Direction.Leftwards
360 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
361 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
363 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
364 // Make autocompletion impossible when !haveApps
365 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
366 }
367 }
368 }
370 Lockscreen {
371 id: lockscreen
372 objectName: "lockscreen"
374 readonly property int backgroundTopMargin: -panel.panelHeight
376 hides: [launcher, panel.indicators, hud]
377 shown: false
378 enabled: true
379 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
380 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
381 y: panel.panelHeight
382 x: required ? 0 : - width
383 width: parent.width
384 height: parent.height - panel.panelHeight
385 background: shell.background
386 pinLength: 4
388 onEntered: LightDM.Greeter.respond(passphrase);
389 onCancel: greeter.show()
391 Component.onCompleted: {
392 if (LightDM.Users.count == 1) {
393 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
394 greeter.selected(0)
395 }
396 }
397 }
399 Connections {
400 target: LightDM.Greeter
402 onShowPrompt: {
403 if (LightDM.Users.count == 1) {
404 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
405 if (text == "PIN") {
406 lockscreen.alphaNumeric = false
407 } else {
408 lockscreen.alphaNumeric = true
409 }
410 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
411 lockscreen.show();
412 }
413 }
415 onAuthenticationComplete: {
416 if (LightDM.Greeter.promptless) {
417 return;
418 }
419 if (LightDM.Greeter.authenticated) {
420 lockscreen.hide();
421 } else {
422 lockscreen.clear(true);
423 }
424 }
425 }
427 Greeter {
428 id: greeter
429 objectName: "greeter"
431 available: true
432 hides: [launcher, panel.indicators, hud]
433 shown: true
435 defaultBackground: shell.background
437 y: panel.panelHeight
438 width: parent.width
439 height: parent.height - panel.panelHeight
441 dragHandleWidth: shell.edgeSize
443 property var previousMainApp: null
444 property var previousSideApp: null
446 function removeApplicationFocus() {
447 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
448 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
449 applicationManager.unfocusCurrentApplication();
450 }
452 function restoreApplicationFocus() {
453 if (greeter.previousMainApp) {
454 applicationManager.focusApplication(greeter.previousMainApp);
455 greeter.previousMainApp = null;
456 }
457 if (greeter.previousSideApp) {
458 applicationManager.focusApplication(greeter.previousSideApp);
459 greeter.previousSideApp = null;
460 }
461 }
463 onShownChanged: {
464 if (shown) {
465 lockscreen.reset();
466 // If there is only one user, we start authenticating with that one here.
467 // If there are more users, the Greeter will handle that
468 if (LightDM.Users.count == 1) {
469 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
470 greeter.selected(0);
471 }
472 greeter.forceActiveFocus();
473 removeApplicationFocus();
474 } else {
475 restoreApplicationFocus();
476 }
477 }
479 onUnlocked: greeter.hide()
480 onSelected: {
481 // Update launcher items for new user
482 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
483 AccountsService.user = user;
484 LauncherModel.setUser(user);
485 }
487 onLeftTeaserPressedChanged: {
488 if (leftTeaserPressed) {
489 launcher.tease();
490 }
491 }
493 Connections {
494 target: applicationManager
495 ignoreUnknownSignals: true
496 // If any app is focused when greeter is open, it's due to a user action
497 // like a snap decision (say, an incoming call).
498 // TODO: these should be protected to only unlock for certain applications / certain usecases
499 // potentially only in connection with a notification.
500 onMainStageFocusedApplicationChanged: {
501 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
502 greeter.previousMainApp = null // make way for new focused app
503 greeter.previousSideApp = null
504 greeter.hide()
505 }
506 }
507 onSideStageFocusedApplicationChanged: {
508 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
509 greeter.previousMainApp = null // make way for new focused app
510 greeter.previousSideApp = null
511 greeter.hide()
512 }
513 }
514 }
515 }
517 InputFilterArea {
518 anchors.fill: parent
519 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
520 || panel.indicators.shown || hud.shown
521 }
523 Connections {
524 id: powerConnection
525 target: Powerd
527 onDisplayPowerStateChange: {
528 // We ignore any display-off signals when the proximity sensor
529 // is active. This usually indicates something like a phone call.
530 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
531 greeter.showNow();
532 }
534 // No reason to chew demo CPU when user isn't watching
535 if (status == Powerd.Off) {
536 edgeDemo.paused = true;
537 } else if (status == Powerd.On) {
538 edgeDemo.paused = false;
539 }
540 }
541 }
543 function showHome() {
544 var animate = !greeter.shown && !stages.shown
545 greeter.hide()
546 dash.setCurrentScope("home.scope", animate, false)
547 stages.hide()
548 }
550 function hideIndicatorMenu(delay) {
551 panel.hideIndicatorMenu(delay);
552 }
554 Item {
555 id: overlay
557 anchors.fill: parent
559 Panel {
560 id: panel
561 anchors.fill: parent //because this draws indicator menus
562 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
563 indicators {
564 hides: [launcher]
565 available: edgeDemo.panelEnabled
566 contentEnabled: edgeDemo.panelContentEnabled
567 }
568 fullscreenMode: shell.fullscreenMode
569 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
571 InputFilterArea {
572 anchors {
573 top: parent.top
574 left: parent.left
575 right: parent.right
576 }
577 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
578 blockInput: true
579 }
580 }
582 Hud {
583 id: hud
585 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
586 height: parent.height
588 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
589 shown: false
590 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
591 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
593 Connections {
594 target: shell.applicationManager
595 onMainStageFocusedApplicationChanged: hud.hide()
596 onSideStageFocusedApplicationChanged: hud.hide()
597 }
598 }
600 Revealer {
601 id: hudRevealer
603 enabled: hud.shown
604 width: hud.width
605 anchors.left: hud.left
606 height: parent.height
607 target: hud.revealerTarget
608 closedValue: height
609 openedValue: 0
610 direction: Qt.RightToLeft
611 orientation: Qt.Vertical
612 handleSize: hud.handleHeight
613 onCloseClicked: target.hide()
614 }
616 Bottombar {
617 id: bottombar
618 theHud: hud
619 anchors.fill: parent
620 enabled: hud.available
621 applicationIsOnForeground: applicationFocused
622 }
624 InputFilterArea {
625 blockInput: launcher.shown
626 anchors {
627 top: parent.top
628 bottom: parent.bottom
629 left: parent.left
630 }
631 width: launcher.width
632 }
634 Launcher {
635 id: launcher
637 readonly property bool dashSwipe: progress > 0
639 anchors.top: parent.top
640 anchors.bottom: parent.bottom
641 width: parent.width
642 dragAreaWidth: shell.edgeSize
643 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
645 onShowDashHome: {
646 if (edgeDemo.running)
647 return;
649 showHome()
650 }
651 onDash: {
652 if (stages.shown) {
653 stages.hide();
654 launcher.hide();
655 }
656 }
657 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
658 onLauncherApplicationSelected:{
659 if (edgeDemo.running)
660 return;
662 greeter.hide()
663 shell.activateApplication(appId)
664 }
665 onShownChanged: {
666 if (shown) {
667 panel.indicators.hide()
668 hud.hide()
669 bottombar.hide()
670 }
671 }
672 }
674 Notifications {
675 id: notifications
677 model: NotificationBackend.Model
678 margin: units.gu(1)
680 anchors {
681 top: parent.top
682 right: parent.right
683 bottom: parent.bottom
684 topMargin: panel.panelHeight
685 }
686 states: [
687 State {
688 name: "narrow"
689 when: overlay.width <= units.gu(60)
690 AnchorChanges { target: notifications; anchors.left: parent.left }
691 },
692 State {
693 name: "wide"
694 when: overlay.width > units.gu(60)
695 AnchorChanges { target: notifications; anchors.left: undefined }
696 PropertyChanges { target: notifications; width: units.gu(38) }
697 }
698 ]
700 InputFilterArea {
701 anchors { left: parent.left; right: parent.right }
702 height: parent.contentHeight
703 blockInput: height > 0
704 }
705 }
706 }
708 focus: true
709 onFocusChanged: if (!focus) forceActiveFocus();
711 InputFilterArea {
712 anchors {
713 top: parent.top
714 bottom: parent.bottom
715 left: parent.left
716 }
717 width: shell.edgeSize
718 blockInput: true
719 }
721 InputFilterArea {
722 anchors {
723 top: parent.top
724 bottom: parent.bottom
725 right: parent.right
726 }
727 width: shell.edgeSize
728 blockInput: true
729 }
731 Binding {
732 target: i18n
733 property: "domain"
734 value: "unity8"
735 }
737 OSKController {
738 anchors.topMargin: panel.panelHeight
739 anchors.fill: parent // as needs to know the geometry of the shell
740 }
742 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
743 MouseArea {
744 anchors.bottom: parent.bottom
745 anchors.left: parent.left
746 anchors.right: parent.right
747 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
749 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
750 }
752 Label {
753 anchors.centerIn: parent
754 visible: applicationManager.fake
755 text: "EARLY ALPHA\nNOT READY FOR USE"
756 color: "lightgrey"
757 opacity: 0.2
758 font.weight: Font.Black
759 horizontalAlignment: Text.AlignHCenter
760 verticalAlignment: Text.AlignVCenter
761 fontSizeMode: Text.Fit
762 rotation: -45
763 scale: Math.min(parent.width, parent.height) / width
764 }
766 EdgeDemo {
767 id: edgeDemo
768 greeter: greeter
769 launcher: launcher
770 dash: dash
771 indicators: panel.indicators
772 underlay: underlay
773 }
775 Connections {
776 target: SessionBroadcast
777 onShowHome: showHome()
778 }