on 1aff3e1 help sign in
unity8/qml/Shell.qml
781 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 if (sideStage.fullyShown) { // if side stage hidden, don't focus it on restore
449 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
450 }
451 applicationManager.unfocusCurrentApplication();
452 }
454 function restoreApplicationFocus() {
455 if (greeter.previousMainApp) {
456 applicationManager.focusApplication(greeter.previousMainApp);
457 greeter.previousMainApp = null;
458 }
459 if (greeter.previousSideApp) {
460 applicationManager.focusApplication(greeter.previousSideApp);
461 greeter.previousSideApp = null;
462 }
463 }
465 onShownChanged: {
466 if (shown) {
467 lockscreen.reset();
468 // If there is only one user, we start authenticating with that one here.
469 // If there are more users, the Greeter will handle that
470 if (LightDM.Users.count == 1) {
471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
472 greeter.selected(0);
473 }
474 greeter.forceActiveFocus();
475 removeApplicationFocus();
476 } else {
477 restoreApplicationFocus();
478 }
479 }
481 onUnlocked: greeter.hide()
482 onSelected: {
483 // Update launcher items for new user
484 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
485 AccountsService.user = user;
486 LauncherModel.setUser(user);
487 }
489 onLeftTeaserPressedChanged: {
490 if (leftTeaserPressed) {
491 launcher.tease();
492 }
493 }
495 Connections {
496 target: applicationManager
497 ignoreUnknownSignals: true
498 // If any app is focused when greeter is open, it's due to a user action
499 // like a snap decision (say, an incoming call).
500 // TODO: these should be protected to only unlock for certain applications / certain usecases
501 // potentially only in connection with a notification.
502 onMainStageFocusedApplicationChanged: {
503 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
504 greeter.previousMainApp = null // make way for new focused app
505 greeter.previousSideApp = null
506 greeter.hide()
507 }
508 }
509 onSideStageFocusedApplicationChanged: {
510 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
511 greeter.previousMainApp = null // make way for new focused app
512 greeter.previousSideApp = null
513 greeter.hide()
514 }
515 }
516 }
517 }
519 InputFilterArea {
520 anchors.fill: parent
521 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
522 || panel.indicators.shown || hud.shown
523 }
525 Connections {
526 id: powerConnection
527 target: Powerd
529 onDisplayPowerStateChange: {
530 // We ignore any display-off signals when the proximity sensor
531 // is active. This usually indicates something like a phone call.
532 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
533 greeter.showNow();
534 }
536 // No reason to chew demo CPU when user isn't watching
537 if (status == Powerd.Off) {
538 edgeDemo.paused = true;
539 } else if (status == Powerd.On) {
540 edgeDemo.paused = false;
541 }
542 }
543 }
545 function showHome() {
546 var animate = !greeter.shown && !stages.shown
547 greeter.hide()
548 dash.setCurrentScope("home.scope", animate, false)
549 stages.hide()
550 }
552 function hideIndicatorMenu(delay) {
553 panel.hideIndicatorMenu(delay);
554 }
556 Item {
557 id: overlay
559 anchors.fill: parent
561 Panel {
562 id: panel
563 anchors.fill: parent //because this draws indicator menus
564 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
565 indicators {
566 hides: [launcher]
567 available: edgeDemo.panelEnabled
568 contentEnabled: edgeDemo.panelContentEnabled
569 }
570 fullscreenMode: shell.fullscreenMode
571 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
573 InputFilterArea {
574 anchors {
575 top: parent.top
576 left: parent.left
577 right: parent.right
578 }
579 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
580 blockInput: true
581 }
582 }
584 Hud {
585 id: hud
587 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
588 height: parent.height
590 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
591 shown: false
592 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
593 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
595 Connections {
596 target: shell.applicationManager
597 onMainStageFocusedApplicationChanged: hud.hide()
598 onSideStageFocusedApplicationChanged: hud.hide()
599 }
600 }
602 Revealer {
603 id: hudRevealer
605 enabled: hud.shown
606 width: hud.width
607 anchors.left: hud.left
608 height: parent.height
609 target: hud.revealerTarget
610 closedValue: height
611 openedValue: 0
612 direction: Qt.RightToLeft
613 orientation: Qt.Vertical
614 handleSize: hud.handleHeight
615 onCloseClicked: target.hide()
616 }
618 Bottombar {
619 id: bottombar
620 theHud: hud
621 anchors.fill: parent
622 enabled: hud.available
623 applicationIsOnForeground: applicationFocused
624 }
626 InputFilterArea {
627 blockInput: launcher.shown
628 anchors {
629 top: parent.top
630 bottom: parent.bottom
631 left: parent.left
632 }
633 width: launcher.width
634 }
636 Launcher {
637 id: launcher
639 readonly property bool dashSwipe: progress > 0
641 anchors.top: parent.top
642 anchors.bottom: parent.bottom
643 width: parent.width
644 dragAreaWidth: shell.edgeSize
645 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
647 onShowDashHome: {
648 if (edgeDemo.running)
649 return;
651 showHome()
652 }
653 onDash: {
654 if (stages.shown) {
655 stages.hide();
656 launcher.hide();
657 }
658 }
659 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
660 onLauncherApplicationSelected:{
661 if (edgeDemo.running)
662 return;
664 greeter.hide()
665 shell.activateApplication(appId)
666 }
667 onShownChanged: {
668 if (shown) {
669 panel.indicators.hide()
670 hud.hide()
671 bottombar.hide()
672 }
673 }
674 }
676 Notifications {
677 id: notifications
679 model: NotificationBackend.Model
680 margin: units.gu(1)
682 anchors {
683 top: parent.top
684 right: parent.right
685 bottom: parent.bottom
686 topMargin: panel.panelHeight
687 }
688 states: [
689 State {
690 name: "narrow"
691 when: overlay.width <= units.gu(60)
692 AnchorChanges { target: notifications; anchors.left: parent.left }
693 },
694 State {
695 name: "wide"
696 when: overlay.width > units.gu(60)
697 AnchorChanges { target: notifications; anchors.left: undefined }
698 PropertyChanges { target: notifications; width: units.gu(38) }
699 }
700 ]
702 InputFilterArea {
703 anchors { left: parent.left; right: parent.right }
704 height: parent.contentHeight
705 blockInput: height > 0
706 }
707 }
708 }
710 focus: true
711 onFocusChanged: if (!focus) forceActiveFocus();
713 InputFilterArea {
714 anchors {
715 top: parent.top
716 bottom: parent.bottom
717 left: parent.left
718 }
719 width: shell.edgeSize
720 blockInput: true
721 }
723 InputFilterArea {
724 anchors {
725 top: parent.top
726 bottom: parent.bottom
727 right: parent.right
728 }
729 width: shell.edgeSize
730 blockInput: true
731 }
733 Binding {
734 target: i18n
735 property: "domain"
736 value: "unity8"
737 }
739 OSKController {
740 anchors.topMargin: panel.panelHeight
741 anchors.fill: parent // as needs to know the geometry of the shell
742 }
744 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
745 MouseArea {
746 anchors.bottom: parent.bottom
747 anchors.left: parent.left
748 anchors.right: parent.right
749 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
751 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
752 }
754 Label {
755 anchors.centerIn: parent
756 visible: applicationManager.fake
757 text: "EARLY ALPHA\nNOT READY FOR USE"
758 color: "lightgrey"
759 opacity: 0.2
760 font.weight: Font.Black
761 horizontalAlignment: Text.AlignHCenter
762 verticalAlignment: Text.AlignVCenter
763 fontSizeMode: Text.Fit
764 rotation: -45
765 scale: Math.min(parent.width, parent.height) / width
766 }
768 EdgeDemo {
769 id: edgeDemo
770 greeter: greeter
771 launcher: launcher
772 dash: dash
773 indicators: panel.indicators
774 underlay: underlay
775 }
777 Connections {
778 target: SessionBroadcast
779 onShowHome: showHome()
780 }