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