on e538b27 help sign in
unity8/qml/Shell.qml
761 lines 27 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 "Components"
32import "SideStage"
33import "Notifications"
34import Unity.Notifications 1.0 as NotificationBackend
36FocusScope {
37 id: shell
39 // this is only here to select the width / height of the window if not running fullscreen
40 property bool tablet: false
41 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
42 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
44 property real edgeSize: units.gu(2)
45 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
46 property url background
47 readonly property real panelHeight: panel.panelHeight
49 property bool dashShown: dash.shown
50 property bool stageScreenshotsReady: {
51 if (sideStage.shown) {
52 if (mainStage.applications.count > 0) {
53 return mainStage.usingScreenshots && sideStage.usingScreenshots;
54 } else {
55 return sideStage.usingScreenshots;
56 }
57 } else {
58 return mainStage.usingScreenshots;
59 }
60 }
62 property var applicationManager: ApplicationManagerWrapper {}
64 Binding {
65 target: LauncherModel
66 property: "applicationManager"
67 value: ApplicationManager
68 }
70 Component.onCompleted: {
71 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
73 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
75 // FIXME: if application focused before shell starts, shell draws on top of it only.
76 // We should detect already running applications on shell start and bring them to the front.
77 applicationManager.unfocusCurrentApplication();
78 }
80 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
81 || !!applicationManager.sideStageFocusedApplication
82 // Used for autopilot testing.
83 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
85 readonly property bool fullscreenMode: {
86 if (greeter.shown || lockscreen.shown) {
87 return false;
88 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
89 return mainStage.switchingFromFullscreenToFullscreen;
90 } else if (applicationManager.mainStageFocusedApplication) {
91 return applicationManager.mainStageFocusedApplication.fullscreen;
92 } else {
93 return false;
94 }
95 }
97 function activateApplication(appId, argument) {
98 if (applicationManager) {
99 // For newly started applications, as it takes them time to draw their first frame
100 // we add a delay before we hide the animation screenshots to compensate.
101 var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
103 var application;
104 application = applicationManager.activateApplication(appId, argument);
105 if (application == null) {
106 return;
107 }
108 edgeDemo.stopDemo();
109 greeter.hide();
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 // This is a dummy image that is needed to determine if the picture url
129 // in backgroundSettings points to a valid picture file.
130 // We can't do this with the real background image because setting a
131 // new source in onStatusChanged triggers a binding loop detection
132 // inside Image, which causes it not to render even though a valid source
133 // would be set. We don't mind about this image staying black and just
134 // use it for verification to populate the source for the real
135 // background image.
136 Image {
137 source: shell.background
138 height: 0
139 width: 0
140 sourceSize.height: 0
141 sourceSize.width: 0
142 onStatusChanged: {
143 if (status == Image.Error && source != shell.defaultBackground) {
144 shell.background = defaultBackground
145 }
146 }
147 }
149 VolumeControl {
150 id: volumeControl
151 }
153 Keys.onVolumeUpPressed: volumeControl.volumeUp()
154 Keys.onVolumeDownPressed: volumeControl.volumeDown()
156 Item {
157 id: underlay
158 objectName: "underlay"
159 anchors.fill: parent
161 // Whether the underlay is fully covered by opaque UI elements.
162 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
164 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
165 || (sideStage.applications && sideStage.applications.count > 0))
167 // Whether the user should see the topmost application surface (if there's one at all).
168 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
169 && !mainStage.usingScreenshots // but want sideStage animating over app surface
173 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
174 // through the translucent parts of the shell surface.
175 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
177 CrossFadeImage {
178 id: backgroundImage
179 objectName: "backgroundImage"
181 anchors.fill: parent
182 source: shell.background
183 fillMode: Image.PreserveAspectCrop
184 }
186 Rectangle {
187 anchors.fill: parent
188 color: "black"
189 opacity: dash.disappearingAnimationProgress
190 }
192 Dash {
193 id: dash
194 objectName: "dash"
196 available: !greeter.shown && !lockscreen.shown
197 hides: [stages, launcher, panel.indicators]
198 shown: disappearingAnimationProgress !== 1.0
199 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
200 // FIXME: unfocus all applications when going back to the dash
201 onEnabledChanged: {
202 if (enabled) {
203 shell.applicationManager.unfocusCurrentApplication()
204 }
205 }
207 anchors {
208 fill: parent
209 topMargin: panel.panelHeight
210 }
212 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
213 opacity: 1.0 - disappearingAnimationProgress
214 property real disappearingAnimationProgress: {
215 if (greeter.shown) {
216 return greeter.showProgress;
217 } else {
218 return stagesOuterContainer.showProgress;
219 }
220 }
222 // FIXME: only necessary because stagesOuterContainer.showProgress and
223 // greeterRevealer.animatedProgress are not animated
224 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
225 }
226 }
228 Item {
229 id: stagesOuterContainer
231 width: parent.width
232 height: parent.height
233 x: launcher.progress
234 Behavior on x {SmoothedAnimation{velocity: 600}}
236 property real showProgress:
237 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
239 Showable {
240 id: stages
241 objectName: "stages"
243 x: width
245 property bool fullyShown: shown && x == 0 && parent.x == 0
246 property bool fullyHidden: !shown && x == width
247 available: !greeter.shown
248 hides: [panel.indicators]
249 shown: false
250 opacity: 1.0
251 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
252 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
254 width: parent.width
255 height: parent.height
257 // close the stages when no focused application remains
258 Connections {
259 target: shell.applicationManager
260 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
261 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
262 ignoreUnknownSignals: true
263 }
265 function closeIfNoApplications() {
266 if (!shell.applicationManager.mainStageFocusedApplication
267 && !shell.applicationManager.sideStageFocusedApplication
268 && shell.applicationManager.mainStageApplications.count == 0
269 && shell.applicationManager.sideStageApplications.count == 0) {
270 stages.hide();
271 }
272 }
274 // show the stages when an application gets the focus
275 Connections {
276 target: shell.applicationManager
277 onMainStageFocusedApplicationChanged: {
278 if (shell.applicationManager.mainStageFocusedApplication) {
279 mainStage.show();
280 stages.show();
281 }
282 }
283 onSideStageFocusedApplicationChanged: {
284 if (shell.applicationManager.sideStageFocusedApplication) {
285 sideStage.show();
286 stages.show();
287 }
288 }
289 ignoreUnknownSignals: true
290 }
292 Stage {
293 id: mainStage
295 anchors.fill: parent
296 fullyShown: stages.fullyShown
297 fullyHidden: stages.fullyHidden
298 shouldUseScreenshots: !fullyShown
299 rightEdgeEnabled: !sideStage.enabled
301 applicationManager: shell.applicationManager
302 rightEdgeDraggingAreaWidth: shell.edgeSize
303 normalApplicationY: shell.panelHeight
305 shown: true
306 function show() {
307 stages.show();
308 }
309 function hide() {
310 }
312 // FIXME: workaround the fact that focusing a main stage application
313 // raises its surface on top of all other surfaces including the ones
314 // that belong to side stage applications.
315 onFocusedApplicationChanged: {
316 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
317 shell.applicationManager.focusApplication(sideStage.focusedApplication);
318 }
319 }
320 }
322 SideStage {
323 id: sideStage
325 applicationManager: shell.applicationManager
326 rightEdgeDraggingAreaWidth: shell.edgeSize
327 normalApplicationY: shell.panelHeight
329 onShownChanged: {
330 if (!shown && mainStage.applications.count == 0) {
331 stages.hide();
332 }
333 }
334 // FIXME: when hiding the side stage, refocus the main stage
335 // application so that it goes in front of the side stage
336 // application and hides it
337 onFullyShownChanged: {
338 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
339 shell.applicationManager.focusApplication(mainStage.focusedApplication);
340 }
341 }
343 enabled: shell.width >= units.gu(100)
344 visible: enabled
345 fullyShown: stages.fullyShown && shown
346 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
347 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
349 available: !greeter.shown && !lockscreen.shown && enabled
350 hides: [launcher, panel.indicators]
351 shown: false
352 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
353 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
355 width: units.gu(40)
356 height: stages.height
357 handleExpanded: sideStageRevealer.pressed
358 }
360 Revealer {
361 id: sideStageRevealer
363 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
364 && sideStage.available
365 direction: Qt.RightToLeft
366 openedValue: parent.width - sideStage.width
367 hintDisplacement: units.gu(3)
368 /* The size of the sidestage handle needs to be bigger than the
369 typical size used for edge detection otherwise it is really
370 hard to grab.
371 */
372 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
373 closedValue: parent.width + sideStage.handleSizeCollapsed
374 target: sideStage
375 x: parent.width - width
376 width: sideStage.width + handleSize * 0.7
377 height: sideStage.height
378 orientation: Qt.Horizontal
379 }
381 DragHandle {
382 id: stagesDragHandle
384 anchors.top: parent.top
385 anchors.bottom: parent.bottom
386 anchors.right: parent.left
388 width: shell.edgeSize
389 direction: Direction.Leftwards
390 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
391 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
393 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
394 // Make autocompletion impossible when !haveApps
395 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
396 }
397 }
398 }
400 Lockscreen {
401 id: lockscreen
402 objectName: "lockscreen"
404 readonly property int backgroundTopMargin: -panel.panelHeight
406 hides: [launcher, panel.indicators]
407 shown: false
408 enabled: true
409 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
410 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
411 y: panel.panelHeight
412 x: required ? 0 : - width
413 width: parent.width
414 height: parent.height - panel.panelHeight
415 background: shell.background
416 pinLength: 4
418 onEntered: LightDM.Greeter.respond(passphrase);
419 onCancel: greeter.show()
421 Component.onCompleted: {
422 if (LightDM.Users.count == 1) {
423 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
424 }
425 }
426 }
428 Connections {
429 target: LightDM.Greeter
431 onShowPrompt: {
432 if (LightDM.Users.count == 1) {
433 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
434 if (text == "PIN") {
435 lockscreen.alphaNumeric = false
436 } else {
437 lockscreen.alphaNumeric = true
438 }
439 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
440 lockscreen.show();
441 }
442 }
444 onAuthenticationComplete: {
445 if (LightDM.Greeter.promptless) {
446 return;
447 }
448 if (LightDM.Greeter.authenticated) {
449 lockscreen.hide();
450 } else {
451 lockscreen.clear(true);
452 }
453 }
454 }
456 Greeter {
457 id: greeter
458 objectName: "greeter"
460 available: true
461 hides: [launcher, panel.indicators]
462 shown: true
464 defaultBackground: shell.background
466 y: panel.panelHeight
467 width: parent.width
468 height: parent.height - panel.panelHeight
470 dragHandleWidth: shell.edgeSize
472 property var previousMainApp: null
473 property var previousSideApp: null
475 function removeApplicationFocus() {
476 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
477 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
478 applicationManager.unfocusCurrentApplication();
479 }
481 function restoreApplicationFocus() {
482 if (greeter.previousMainApp) {
483 applicationManager.focusApplication(greeter.previousMainApp);
484 greeter.previousMainApp = null;
485 }
486 if (greeter.previousSideApp) {
487 applicationManager.focusApplication(greeter.previousSideApp);
488 greeter.previousSideApp = null;
489 }
490 }
492 onShownChanged: {
493 if (shown) {
494 lockscreen.reset();
495 // If there is only one user, we start authenticating with that one here.
496 // If there are more users, the Greeter will handle that
497 if (LightDM.Users.count == 1) {
498 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
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 onFocusRequested: shell.activateApplication(appId)
543 }
544 }
546 InputFilterArea {
547 anchors.fill: parent
548 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
549 || panel.indicators.shown
550 }
552 Connections {
553 id: powerConnection
554 target: Powerd
556 onDisplayPowerStateChange: {
557 // We ignore any display-off signals when the proximity sensor
558 // is active. This usually indicates something like a phone call.
559 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
560 greeter.showNow();
561 }
563 // No reason to chew demo CPU when user isn't watching
564 if (status == Powerd.Off) {
565 edgeDemo.paused = true;
566 } else if (status == Powerd.On) {
567 edgeDemo.paused = false;
568 }
569 }
570 }
572 function showHome() {
573 var animate = !greeter.shown && !stages.shown
574 greeter.hide()
575 dash.setCurrentScope("home.scope", animate, false)
576 stages.hide()
577 }
579 function hideIndicatorMenu(delay) {
580 panel.hideIndicatorMenu(delay);
581 }
583 Item {
584 id: overlay
586 anchors.fill: parent
588 Panel {
589 id: panel
590 anchors.fill: parent //because this draws indicator menus
591 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
592 indicators {
593 hides: [launcher]
594 available: edgeDemo.panelEnabled
595 contentEnabled: edgeDemo.panelContentEnabled
596 }
597 fullscreenMode: shell.fullscreenMode
598 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
600 InputFilterArea {
601 anchors {
602 top: parent.top
603 left: parent.left
604 right: parent.right
605 }
606 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
607 blockInput: true
608 }
609 }
611 InputFilterArea {
612 blockInput: launcher.shown
613 anchors {
614 top: parent.top
615 bottom: parent.bottom
616 left: parent.left
617 }
618 width: launcher.width
619 }
621 Launcher {
622 id: launcher
624 readonly property bool dashSwipe: progress > 0
626 anchors.top: parent.top
627 anchors.bottom: parent.bottom
628 width: parent.width
629 dragAreaWidth: shell.edgeSize
630 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
632 onShowDashHome: {
633 if (edgeDemo.running)
634 return;
636 showHome()
637 }
638 onDash: {
639 if (stages.shown) {
640 stages.hide();
641 launcher.hide();
642 }
643 }
644 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
645 onLauncherApplicationSelected: {
646 if (!edgeDemo.running)
647 shell.activateApplication(appId)
648 }
649 onShownChanged: {
650 if (shown) {
651 panel.indicators.hide()
652 }
653 }
654 }
656 Notifications {
657 id: notifications
659 model: NotificationBackend.Model
660 margin: units.gu(1)
662 anchors {
663 top: parent.top
664 right: parent.right
665 bottom: parent.bottom
666 topMargin: panel.panelHeight
667 }
668 states: [
669 State {
670 name: "narrow"
671 when: overlay.width <= units.gu(60)
672 AnchorChanges { target: notifications; anchors.left: parent.left }
673 },
674 State {
675 name: "wide"
676 when: overlay.width > units.gu(60)
677 AnchorChanges { target: notifications; anchors.left: undefined }
678 PropertyChanges { target: notifications; width: units.gu(38) }
679 }
680 ]
682 InputFilterArea {
683 anchors { left: parent.left; right: parent.right }
684 height: parent.contentHeight
685 blockInput: height > 0
686 }
687 }
688 }
690 focus: true
691 onFocusChanged: if (!focus) forceActiveFocus();
693 InputFilterArea {
694 anchors {
695 top: parent.top
696 bottom: parent.bottom
697 left: parent.left
698 }
699 width: shell.edgeSize
700 blockInput: true
701 }
703 InputFilterArea {
704 anchors {
705 top: parent.top
706 bottom: parent.bottom
707 right: parent.right
708 }
709 width: shell.edgeSize
710 blockInput: true
711 }
713 Binding {
714 target: i18n
715 property: "domain"
716 value: "unity8"
717 }
719 OSKController {
720 anchors.topMargin: panel.panelHeight
721 anchors.fill: parent // as needs to know the geometry of the shell
722 }
724 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
725 MouseArea {
726 anchors.bottom: parent.bottom
727 anchors.left: parent.left
728 anchors.right: parent.right
729 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
731 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
732 }
734 Label {
735 anchors.centerIn: parent
736 visible: applicationManager.fake
737 text: "EARLY ALPHA\nNOT READY FOR USE"
738 color: "lightgrey"
739 opacity: 0.2
740 font.weight: Font.Black
741 horizontalAlignment: Text.AlignHCenter
742 verticalAlignment: Text.AlignVCenter
743 fontSizeMode: Text.Fit
744 rotation: -45
745 scale: Math.min(parent.width, parent.height) / width
746 }
748 EdgeDemo {
749 id: edgeDemo
750 greeter: greeter
751 launcher: launcher
752 dash: dash
753 indicators: panel.indicators
754 underlay: underlay
755 }
757 Connections {
758 target: SessionBroadcast
759 onShowHome: showHome()
760 }