on 7ce2be3 help sign in
unity8/qml/Shell.qml
870 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.Components.Popups 0.1
23import Ubuntu.Gestures 0.1
24import Unity.Launcher 0.1
25import LightDM 0.1 as LightDM
26import Powerd 0.1
27import SessionBroadcast 0.1
28import "Dash"
29import "Greeter"
30import "Launcher"
31import "Panel"
32import "Components"
33import "Notifications"
34import Unity.Notifications 1.0 as NotificationBackend
35import Unity.Session 0.1
37FocusScope {
38 id: shell
40 // this is only here to select the width / height of the window if not running fullscreen
41 property bool tablet: false
42 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
45 property real edgeSize: units.gu(2)
46 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
47 property url background
48 readonly property real panelHeight: panel.panelHeight
50 property bool dashShown: dash.shown
52 property bool sideStageEnabled: shell.width >= units.gu(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 stages.show(true);
59 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60 applicationsDisplayLoader.item.select(appId);
61 }
62 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show(false);
66 }
67 }
69 Binding {
70 target: LauncherModel
71 property: "applicationManager"
72 value: ApplicationManager
73 }
75 Component.onCompleted: {
76 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
77 }
79 GSettings {
80 id: backgroundSettings
81 schema.id: "org.gnome.desktop.background"
82 }
83 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
84 onGSettingsPictureChanged: {
85 shell.background = gSettingsPicture
86 }
88 VolumeControl {
89 id: volumeControl
90 }
92 Keys.onVolumeUpPressed: volumeControl.volumeUp()
93 Keys.onVolumeDownPressed: volumeControl.volumeDown()
95 Item {
96 id: underlayClipper
97 anchors.fill: parent
98 anchors.rightMargin: stages.overlayWidth
99 clip: stages.overlayMode && !stages.painting
101 InputFilterArea {
102 anchors.fill: parent
103 blockInput: parent.clip
104 }
106 Item {
107 id: underlay
108 objectName: "underlay"
109 anchors.fill: parent
110 anchors.rightMargin: -parent.anchors.rightMargin
112 // Whether the underlay is fully covered by opaque UI elements.
113 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicators.width
115 // Whether the user should see the topmost application surface (if there's one at all).
116 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
118 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119 // through the translucent parts of the shell surface.
120 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
122 Rectangle {
123 anchors.fill: parent
124 color: "black"
125 opacity: dash.disappearingAnimationProgress
126 }
128 Dash {
129 id: dash
130 objectName: "dash"
132 available: !greeter.shown && !lockscreen.shown
133 hides: [stages, launcher, panel.indicators]
134 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
135 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
137 overviewHandleHeight: shell.edgeSize
139 anchors {
140 fill: parent
141 topMargin: panel.panelHeight
142 }
144 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
145 opacity: 1.0 - disappearingAnimationProgress
146 property real disappearingAnimationProgress: {
147 if (stages.overlayMode) {
148 return 0;
149 } else {
150 return stages.showProgress
151 }
152 }
154 // FIXME: only necessary because stages.showProgress is not animated
155 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
156 }
157 }
158 }
160 EdgeDragArea {
161 id: stagesDragHandle
162 direction: Direction.Leftwards
164 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
165 width: shell.edgeSize
167 property real progress: stages.width
169 onTouchXChanged: {
170 if (status == DirectionalDragArea.Recognized) {
171 if (ApplicationManager.count == 0) {
172 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
173 } else {
174 progress = stages.width - stagesDragHandle.width + touchX
175 }
176 }
177 }
179 onDraggingChanged: {
180 if (!dragging) {
181 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
182 stages.show(true)
183 }
184 stagesDragHandle.progress = stages.width;
185 }
186 }
187 }
189 Item {
190 id: stages
191 objectName: "stages"
192 width: parent.width
193 height: parent.height
195 x: {
196 if (shown) {
197 if (overlayMode || locked) {
198 return 0;
199 }
200 return launcher.progress
201 } else {
202 return stagesDragHandle.progress
203 }
204 }
206 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
208 property bool shown: false
210 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
212 property bool fullyShown: x == 0
213 property bool fullyHidden: x == width
215 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
216 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
217 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
218 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
219 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
221 function show(focusApp) {
222 shown = true;
223 panel.indicators.hide();
224 edgeDemo.stopDemo();
225 greeter.hide();
226 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
227 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
228 }
229 }
231 function hide() {
232 shown = false;
233 if (ApplicationManager.focusedApplicationId) {
234 ApplicationManager.unfocusCurrentApplication();
235 }
236 }
238 Connections {
239 target: ApplicationManager
241 onFocusRequested: {
242 stages.show(true);
243 }
245 onFocusedApplicationIdChanged: {
246 if (ApplicationManager.focusedApplicationId.length > 0) {
247 stages.show(false);
248 } else {
249 if (!stages.overlayMode) {
250 stages.hide();
251 }
252 }
253 }
255 onApplicationAdded: {
256 stages.show(false);
257 }
259 onApplicationRemoved: {
260 if (ApplicationManager.focusedApplicationId.length == 0) {
261 stages.hide();
262 }
263 }
264 }
266 property bool dialogShown: false
268 Component {
269 id: logoutDialog
270 Dialog {
271 id: dialogueLogout
272 title: "Logout"
273 text: "Are you sure that you want to logout?"
274 Button {
275 text: "Cancel"
276 onClicked: {
277 PopupUtils.close(dialogueLogout);
278 stages.dialogShown = false;
279 }
280 }
281 Button {
282 text: "Yes"
283 onClicked: {
284 DBusUnitySessionService.Logout();
285 PopupUtils.close(dialogueLogout);
286 stages.dialogShown = false;
287 }
288 }
289 }
290 }
292 Component {
293 id: shutdownDialog
294 Dialog {
295 id: dialogueShutdown
296 title: "Shutdown"
297 text: "Are you sure that you want to shutdown?"
298 Button {
299 text: "Cancel"
300 onClicked: {
301 PopupUtils.close(dialogueShutdown);
302 stages.dialogShown = false;
303 }
304 }
305 Button {
306 text: "Yes"
307 onClicked: {
308 dBusUnitySessionServiceConnection.closeAllApps();
309 DBusUnitySessionService.Shutdown();
310 PopupUtils.close(dialogueShutdown);
311 stages.dialogShown = false;
312 }
313 }
314 }
315 }
317 Component {
318 id: rebootDialog
319 Dialog {
320 id: dialogueReboot
321 title: "Reboot"
322 text: "Are you sure that you want to reboot?"
323 Button {
324 text: "Cancel"
325 onClicked: {
326 PopupUtils.close(dialogueReboot)
327 stages.dialogShown = false;
328 }
329 }
330 Button {
331 text: "Yes"
332 onClicked: {
333 dBusUnitySessionServiceConnection.closeAllApps();
334 DBusUnitySessionService.Reboot();
335 PopupUtils.close(dialogueReboot);
336 stages.dialogShown = false;
337 }
338 }
339 }
340 }
342 Component {
343 id: powerDialog
344 Dialog {
345 id: dialoguePower
346 title: "Power"
347 text: i18n.tr("Are you sure you would like to turn power off?")
348 Button {
349 text: i18n.tr("Power off")
350 onClicked: {
351 dBusUnitySessionServiceConnection.closeAllApps();
352 PopupUtils.close(dialoguePower);
353 stages.dialogShown = false;
354 shutdownFadeOutRectangle.enabled = true;
355 shutdownFadeOutRectangle.visible = true;
356 shutdownFadeOut.start();
357 }
358 }
359 Button {
360 text: i18n.tr("Restart")
361 onClicked: {
362 dBusUnitySessionServiceConnection.closeAllApps();
363 DBusUnitySessionService.Reboot();
364 PopupUtils.close(dialoguePower);
365 stages.dialogShown = false;
366 }
367 }
368 Button {
369 text: i18n.tr("Cancel")
370 onClicked: {
371 PopupUtils.close(dialoguePower);
372 stages.dialogShown = false;
373 }
374 }
375 }
376 }
378 function showPowerDialog() {
379 if (!stages.dialogShown) {
380 stages.dialogShown = true;
381 PopupUtils.open(powerDialog);
382 }
383 }
385 Connections {
386 id: dBusUnitySessionServiceConnection
387 objectName: "dBusUnitySessionServiceConnection"
388 target: DBusUnitySessionService
390 function closeAllApps() {
391 while (true) {
392 var app = ApplicationManager.get(0);
393 if (app === null) {
394 break;
395 }
396 ApplicationManager.stopApplication(app.appId);
397 }
398 }
400 onLogoutRequested: {
401 // Display a dialog to ask the user to confirm.
402 if (!stages.dialogShown) {
403 stages.dialogShown = true;
404 PopupUtils.open(logoutDialog);
405 }
406 }
408 onShutdownRequested: {
409 // Display a dialog to ask the user to confirm.
410 if (!stages.dialogShown) {
411 stages.dialogShown = true;
412 PopupUtils.open(shutdownDialog);
413 }
414 }
416 onRebootRequested: {
417 // Display a dialog to ask the user to confirm.
418 if (!stages.dialogShown) {
419 stages.dialogShown = true;
420 PopupUtils.open(rebootDialog);
421 }
422 }
424 onLogoutReady: {
425 closeAllApps();
426 Qt.quit();
427 }
428 }
430 Loader {
431 id: applicationsDisplayLoader
432 anchors.fill: parent
434 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
436 Binding {
437 target: applicationsDisplayLoader.item
438 property: "moving"
439 value: !stages.fullyShown
440 }
441 Binding {
442 target: applicationsDisplayLoader.item
443 property: "shown"
444 value: stages.shown
445 }
446 Binding {
447 target: applicationsDisplayLoader.item
448 property: "dragAreaWidth"
449 value: shell.edgeSize
450 }
451 }
452 }
454 Lockscreen {
455 id: lockscreen
456 objectName: "lockscreen"
458 readonly property int backgroundTopMargin: -panel.panelHeight
460 hides: [launcher, panel.indicators]
461 shown: false
462 enabled: true
463 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
464 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
465 y: panel.panelHeight
466 x: required ? 0 : - width
467 width: parent.width
468 height: parent.height - panel.panelHeight
469 background: shell.background
470 minPinLength: 4
471 maxPinLength: 4
473 onEntered: LightDM.Greeter.respond(passphrase);
474 onCancel: greeter.show()
476 Component.onCompleted: {
477 if (LightDM.Users.count == 1) {
478 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
479 }
480 }
481 }
483 Connections {
484 target: LightDM.Greeter
486 onShowGreeter: greeter.show()
488 onShowPrompt: {
489 if (LightDM.Users.count == 1) {
490 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
491 if (text == "PIN") {
492 lockscreen.alphaNumeric = false
493 } else {
494 lockscreen.alphaNumeric = true
495 }
496 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
497 lockscreen.show();
498 }
499 }
501 onAuthenticationComplete: {
502 if (LightDM.Greeter.promptless) {
503 return;
504 }
505 if (LightDM.Greeter.authenticated) {
506 lockscreen.hide();
507 } else {
508 lockscreen.clear(true);
509 }
510 }
511 }
513 Binding {
514 target: LightDM.Greeter
515 property: "active"
516 value: greeter.shown || lockscreen.shown
517 }
519 Rectangle {
520 anchors.fill: parent
521 color: "black"
522 opacity: greeterWrapper.showProgress * 0.8
523 }
525 Item {
526 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
527 id: greeterWrapper
528 x: launcher.progress
529 y: panel.panelHeight
530 width: parent.width
531 height: parent.height - panel.panelHeight
533 Behavior on x {
534 enabled: !launcher.dashSwipe
535 StandardAnimation {}
536 }
538 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
540 Greeter {
541 id: greeter
542 objectName: "greeter"
544 available: true
545 hides: [launcher, panel.indicators]
546 shown: true
548 defaultBackground: shell.background
550 width: parent.width
551 height: parent.height
553 dragHandleWidth: shell.edgeSize
555 onShownChanged: {
556 if (shown) {
557 lockscreen.reset();
558 if (!LightDM.Greeter.promptless) {
559 lockscreen.show();
560 }
561 // If there is only one user, we start authenticating with that one here.
562 // If there are more users, the Greeter will handle that
563 if (LightDM.Users.count == 1) {
564 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
565 }
566 greeter.forceActiveFocus();
567 }
568 }
570 onUnlocked: greeter.hide()
571 onSelected: {
572 // Update launcher items for new user
573 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
574 AccountsService.user = user;
575 LauncherModel.setUser(user);
576 }
578 onTease: launcher.tease()
580 Binding {
581 target: ApplicationManager
582 property: "suspended"
583 value: greeter.shown && greeterWrapper.showProgress == 1
584 }
585 }
586 }
588 InputFilterArea {
589 anchors.fill: parent
590 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
591 || panel.indicators.shown
592 }
594 Connections {
595 id: powerConnection
596 target: Powerd
598 onDisplayPowerStateChange: {
599 // We ignore any display-off signals when the proximity sensor
600 // is active. This usually indicates something like a phone call.
601 if (status == Powerd.Off && reason != Powerd.Proximity) {
602 greeter.showNow();
603 }
605 // No reason to chew demo CPU when user isn't watching
606 if (status == Powerd.Off) {
607 edgeDemo.paused = true;
608 } else if (status == Powerd.On) {
609 edgeDemo.paused = false;
610 }
611 }
612 }
614 function showHome() {
615 var animate = !greeter.shown && !stages.shown
616 greeter.hide()
617 dash.setCurrentScope("clickscope", animate, false)
618 stages.hide()
619 }
621 Item {
622 id: overlay
624 anchors.fill: parent
626 Panel {
627 id: panel
628 anchors.fill: parent //because this draws indicator menus
629 indicators {
630 hides: [launcher]
631 available: edgeDemo.panelEnabled
632 contentEnabled: edgeDemo.panelContentEnabled
633 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
634 panelHeight: units.gu(3)
635 }
636 property string focusedAppId: ApplicationManager.focusedApplicationId
637 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
638 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
640 InputFilterArea {
641 anchors {
642 top: parent.top
643 left: parent.left
644 right: parent.right
645 }
646 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
647 blockInput: true
648 }
649 }
651 InputFilterArea {
652 blockInput: launcher.shown
653 anchors {
654 top: parent.top
655 bottom: parent.bottom
656 left: parent.left
657 }
658 width: launcher.width
659 }
661 Launcher {
662 id: launcher
664 readonly property bool dashSwipe: progress > 0
666 anchors.top: parent.top
667 anchors.bottom: parent.bottom
668 width: parent.width
669 dragAreaWidth: shell.edgeSize
670 available: edgeDemo.launcherEnabled
672 onShowDashHome: {
673 if (edgeDemo.running)
674 return;
676 showHome()
677 }
678 onDash: {
679 if (stages.shown && !stages.overlayMode && !stages.locked) {
680 stages.hide();
681 launcher.fadeOut();
682 } else {
683 launcher.switchToNextState("visible");
684 }
686 if (greeter.shown) {
687 greeter.hideRight();
688 launcher.fadeOut();
689 }
690 }
691 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
692 onLauncherApplicationSelected: {
693 if (!edgeDemo.running)
694 shell.activateApplication(appId)
695 }
696 onShownChanged: {
697 if (shown) {
698 panel.indicators.hide()
699 }
700 }
701 }
703 Rectangle {
704 id: modalNotificationBackground
706 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
707 color: "#000000"
708 anchors.fill: parent
709 opacity: 0.5
711 MouseArea {
712 anchors.fill: parent
713 }
715 InputFilterArea {
716 anchors.fill: parent
717 blockInput: modalNotificationBackground.visible
718 }
719 }
721 Notifications {
722 id: notifications
724 model: NotificationBackend.Model
725 margin: units.gu(1)
727 y: panel.panelHeight
728 width: parent.width
729 height: parent.height - panel.panelHeight
731 states: [
732 State {
733 name: "narrow"
734 when: overlay.width <= units.gu(60)
735 AnchorChanges { target: notifications; anchors.left: parent.left }
736 },
737 State {
738 name: "wide"
739 when: overlay.width > units.gu(60)
740 AnchorChanges { target: notifications; anchors.left: undefined }
741 PropertyChanges { target: notifications; width: units.gu(38) }
742 }
743 ]
745 InputFilterArea {
746 anchors { left: parent.left; right: parent.right }
747 height: parent.contentHeight
748 blockInput: height > 0
749 }
750 }
751 }
753 focus: true
754 onFocusChanged: if (!focus) forceActiveFocus();
756 InputFilterArea {
757 anchors {
758 top: parent.top
759 bottom: parent.bottom
760 left: parent.left
761 }
762 width: shell.edgeSize
763 blockInput: true
764 }
766 InputFilterArea {
767 anchors {
768 top: parent.top
769 bottom: parent.bottom
770 right: parent.right
771 }
772 width: shell.edgeSize
773 blockInput: true
774 }
776 Binding {
777 target: i18n
778 property: "domain"
779 value: "unity8"
780 }
782 OSKController {
783 anchors.topMargin: panel.panelHeight
784 anchors.fill: parent // as needs to know the geometry of the shell
785 }
787 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
788 MouseArea {
789 anchors.bottom: parent.bottom
790 anchors.left: parent.left
791 anchors.right: parent.right
792 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
794 enabled: ApplicationManager.keyboardVisible
795 }
797 Label {
798 anchors.centerIn: parent
799 visible: ApplicationManager.fake ? ApplicationManager.fake : false
800 text: "EARLY ALPHA\nNOT READY FOR USE"
801 color: "lightgrey"
802 opacity: 0.2
803 font.weight: Font.Black
804 horizontalAlignment: Text.AlignHCenter
805 verticalAlignment: Text.AlignVCenter
806 fontSizeMode: Text.Fit
807 rotation: -45
808 scale: Math.min(parent.width, parent.height) / width
809 }
811 EdgeDemo {
812 id: edgeDemo
813 greeter: greeter
814 launcher: launcher
815 dash: dash
816 indicators: panel.indicators
817 underlay: underlay
818 }
820 Connections {
821 target: SessionBroadcast
822 onShowHome: showHome()
823 }
825 Keys.onPressed: {
826 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
827 if (!powerKeyTimer.running) {
828 powerKeyTimer.start();
829 }
830 event.accepted = true;
831 }
832 }
834 Keys.onReleased: {
835 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
836 powerKeyTimer.stop();
837 event.accepted = true;
838 }
839 }
841 Timer {
842 id: powerKeyTimer
843 interval: 2000
844 repeat: false
845 triggeredOnStart: false
847 onTriggered: {
848 stages.showPowerDialog();
849 }
850 }
852 Rectangle {
853 id: shutdownFadeOutRectangle
854 enabled: false
855 visible: false
856 color: "black"
857 anchors.fill: parent
858 opacity: 0.0
859 NumberAnimation on opacity {
860 id: shutdownFadeOut
861 from: 0.0
862 to: 1.0
863 onStopped: {
864 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
865 DBusUnitySessionService.Shutdown();
866 }
867 }
868 }
869 }