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