on 4fdd107 help sign in
unity8/qml/Shell.qml
876 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 Image {
129 anchors.fill: dash
130 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
131 fillMode: Image.PreserveAspectCrop
132 horizontalAlignment: Image.AlignRight
133 verticalAlignment: Image.AlignTop
134 }
136 Dash {
137 id: dash
138 objectName: "dash"
140 available: !greeter.shown && !lockscreen.shown
141 hides: [stages, launcher, panel.indicators]
142 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
143 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
145 anchors {
146 fill: parent
147 topMargin: panel.panelHeight
148 }
150 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
151 opacity: 1.0 - disappearingAnimationProgress
152 property real disappearingAnimationProgress: {
153 if (stages.overlayMode) {
154 return 0;
155 } else {
156 return stages.showProgress
157 }
158 }
160 // FIXME: only necessary because stages.showProgress is not animated
161 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
162 }
163 }
164 }
166 EdgeDragArea {
167 id: stagesDragHandle
168 direction: Direction.Leftwards
170 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
171 width: shell.edgeSize
173 property real progress: stages.width
175 onTouchXChanged: {
176 if (status == DirectionalDragArea.Recognized) {
177 if (ApplicationManager.count == 0) {
178 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
179 } else {
180 progress = stages.width - stagesDragHandle.width + touchX
181 }
182 }
183 }
185 onDraggingChanged: {
186 if (!dragging) {
187 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
188 stages.show(true)
189 }
190 stagesDragHandle.progress = stages.width;
191 }
192 }
193 }
195 Item {
196 id: stages
197 objectName: "stages"
198 width: parent.width
199 height: parent.height
201 x: {
202 if (shown) {
203 if (overlayMode || locked) {
204 return 0;
205 }
206 return launcher.progress
207 } else {
208 return stagesDragHandle.progress
209 }
210 }
212 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
214 property bool shown: false
216 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
218 property bool fullyShown: x == 0
219 property bool fullyHidden: x == width
221 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
222 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
223 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
224 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
225 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
227 function show(focusApp) {
228 shown = true;
229 panel.indicators.hide();
230 edgeDemo.stopDemo();
231 greeter.hide();
232 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
233 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
234 }
235 }
237 function hide() {
238 shown = false;
239 if (ApplicationManager.focusedApplicationId) {
240 ApplicationManager.unfocusCurrentApplication();
241 }
242 }
244 Connections {
245 target: ApplicationManager
247 onFocusRequested: {
248 stages.show(true);
249 }
251 onFocusedApplicationIdChanged: {
252 if (ApplicationManager.focusedApplicationId.length > 0) {
253 stages.show(false);
254 } else {
255 if (!stages.overlayMode) {
256 stages.hide();
257 }
258 }
259 }
261 onApplicationAdded: {
262 stages.show(false);
263 }
265 onApplicationRemoved: {
266 if (ApplicationManager.focusedApplicationId.length == 0) {
267 stages.hide();
268 }
269 }
270 }
272 property bool dialogShown: false
274 Component {
275 id: logoutDialog
276 Dialog {
277 id: dialogueLogout
278 title: "Logout"
279 text: "Are you sure that you want to logout?"
280 Button {
281 text: "Cancel"
282 onClicked: {
283 PopupUtils.close(dialogueLogout);
284 stages.dialogShown = false;
285 }
286 }
287 Button {
288 text: "Yes"
289 onClicked: {
290 DBusUnitySessionService.Logout();
291 PopupUtils.close(dialogueLogout);
292 stages.dialogShown = false;
293 }
294 }
295 }
296 }
298 Component {
299 id: shutdownDialog
300 Dialog {
301 id: dialogueShutdown
302 title: "Shutdown"
303 text: "Are you sure that you want to shutdown?"
304 Button {
305 text: "Cancel"
306 onClicked: {
307 PopupUtils.close(dialogueShutdown);
308 stages.dialogShown = false;
309 }
310 }
311 Button {
312 text: "Yes"
313 onClicked: {
314 dBusUnitySessionServiceConnection.closeAllApps();
315 DBusUnitySessionService.Shutdown();
316 PopupUtils.close(dialogueShutdown);
317 stages.dialogShown = false;
318 }
319 }
320 }
321 }
323 Component {
324 id: rebootDialog
325 Dialog {
326 id: dialogueReboot
327 title: "Reboot"
328 text: "Are you sure that you want to reboot?"
329 Button {
330 text: "Cancel"
331 onClicked: {
332 PopupUtils.close(dialogueReboot)
333 stages.dialogShown = false;
334 }
335 }
336 Button {
337 text: "Yes"
338 onClicked: {
339 dBusUnitySessionServiceConnection.closeAllApps();
340 DBusUnitySessionService.Reboot();
341 PopupUtils.close(dialogueReboot);
342 stages.dialogShown = false;
343 }
344 }
345 }
346 }
348 Component {
349 id: powerDialog
350 Dialog {
351 id: dialoguePower
352 title: "Power"
353 text: i18n.tr("Are you sure you would like to turn power off?")
354 Button {
355 text: i18n.tr("Power off")
356 onClicked: {
357 dBusUnitySessionServiceConnection.closeAllApps();
358 PopupUtils.close(dialoguePower);
359 stages.dialogShown = false;
360 shutdownFadeOutRectangle.enabled = true;
361 shutdownFadeOutRectangle.visible = true;
362 shutdownFadeOut.start();
363 }
364 }
365 Button {
366 text: i18n.tr("Restart")
367 onClicked: {
368 dBusUnitySessionServiceConnection.closeAllApps();
369 DBusUnitySessionService.Reboot();
370 PopupUtils.close(dialoguePower);
371 stages.dialogShown = false;
372 }
373 }
374 Button {
375 text: i18n.tr("Cancel")
376 onClicked: {
377 PopupUtils.close(dialoguePower);
378 stages.dialogShown = false;
379 }
380 }
381 }
382 }
384 function showPowerDialog() {
385 if (!stages.dialogShown) {
386 stages.dialogShown = true;
387 PopupUtils.open(powerDialog);
388 }
389 }
391 Connections {
392 id: dBusUnitySessionServiceConnection
393 objectName: "dBusUnitySessionServiceConnection"
394 target: DBusUnitySessionService
396 function closeAllApps() {
397 while (true) {
398 var app = ApplicationManager.get(0);
399 if (app === null) {
400 break;
401 }
402 ApplicationManager.stopApplication(app.appId);
403 }
404 }
406 onLogoutRequested: {
407 // Display a dialog to ask the user to confirm.
408 if (!stages.dialogShown) {
409 stages.dialogShown = true;
410 PopupUtils.open(logoutDialog);
411 }
412 }
414 onShutdownRequested: {
415 // Display a dialog to ask the user to confirm.
416 if (!stages.dialogShown) {
417 stages.dialogShown = true;
418 PopupUtils.open(shutdownDialog);
419 }
420 }
422 onRebootRequested: {
423 // Display a dialog to ask the user to confirm.
424 if (!stages.dialogShown) {
425 stages.dialogShown = true;
426 PopupUtils.open(rebootDialog);
427 }
428 }
430 onLogoutReady: {
431 closeAllApps();
432 Qt.quit();
433 }
434 }
436 Loader {
437 id: applicationsDisplayLoader
438 anchors.fill: parent
440 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
442 Binding {
443 target: applicationsDisplayLoader.item
444 property: "moving"
445 value: !stages.fullyShown
446 }
447 Binding {
448 target: applicationsDisplayLoader.item
449 property: "shown"
450 value: stages.shown
451 }
452 Binding {
453 target: applicationsDisplayLoader.item
454 property: "dragAreaWidth"
455 value: shell.edgeSize
456 }
457 }
458 }
460 Lockscreen {
461 id: lockscreen
462 objectName: "lockscreen"
464 readonly property int backgroundTopMargin: -panel.panelHeight
466 hides: [launcher, panel.indicators]
467 shown: false
468 enabled: true
469 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
470 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
471 y: panel.panelHeight
472 x: required ? 0 : - width
473 width: parent.width
474 height: parent.height - panel.panelHeight
475 background: shell.background
476 minPinLength: 4
477 maxPinLength: 4
479 onEntered: LightDM.Greeter.respond(passphrase);
480 onCancel: greeter.show()
482 Component.onCompleted: {
483 if (LightDM.Users.count == 1) {
484 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
485 }
486 }
487 }
489 Connections {
490 target: LightDM.Greeter
492 onShowGreeter: greeter.show()
494 onShowPrompt: {
495 if (LightDM.Users.count == 1) {
496 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
497 if (text == "PIN") {
498 lockscreen.alphaNumeric = false
499 } else {
500 lockscreen.alphaNumeric = true
501 }
502 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
503 lockscreen.show();
504 }
505 }
507 onAuthenticationComplete: {
508 if (LightDM.Greeter.promptless) {
509 return;
510 }
511 if (LightDM.Greeter.authenticated) {
512 lockscreen.hide();
513 } else {
514 lockscreen.clear(true);
515 }
516 }
517 }
519 Binding {
520 target: LightDM.Greeter
521 property: "active"
522 value: greeter.shown || lockscreen.shown
523 }
525 Rectangle {
526 anchors.fill: parent
527 color: "black"
528 opacity: greeterWrapper.showProgress * 0.8
529 }
531 Item {
532 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
533 id: greeterWrapper
534 x: launcher.progress
535 y: panel.panelHeight
536 width: parent.width
537 height: parent.height - panel.panelHeight
539 Behavior on x {
540 enabled: !launcher.dashSwipe
541 StandardAnimation {}
542 }
544 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
546 Greeter {
547 id: greeter
548 objectName: "greeter"
550 available: true
551 hides: [launcher, panel.indicators]
552 shown: true
554 defaultBackground: shell.background
556 width: parent.width
557 height: parent.height
559 dragHandleWidth: shell.edgeSize
561 onShownChanged: {
562 if (shown) {
563 lockscreen.reset();
564 if (!LightDM.Greeter.promptless) {
565 lockscreen.show();
566 }
567 // If there is only one user, we start authenticating with that one here.
568 // If there are more users, the Greeter will handle that
569 if (LightDM.Users.count == 1) {
570 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
571 }
572 greeter.forceActiveFocus();
573 }
574 }
576 onUnlocked: greeter.hide()
577 onSelected: {
578 // Update launcher items for new user
579 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
580 AccountsService.user = user;
581 LauncherModel.setUser(user);
582 }
584 onTease: launcher.tease()
586 Binding {
587 target: ApplicationManager
588 property: "suspended"
589 value: greeter.shown && greeterWrapper.showProgress == 1
590 }
591 }
592 }
594 InputFilterArea {
595 anchors.fill: parent
596 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
597 || panel.indicators.shown
598 }
600 Connections {
601 id: powerConnection
602 target: Powerd
604 onDisplayPowerStateChange: {
605 // We ignore any display-off signals when the proximity sensor
606 // is active. This usually indicates something like a phone call.
607 if (status == Powerd.Off && reason != Powerd.Proximity) {
608 greeter.showNow();
609 }
611 // No reason to chew demo CPU when user isn't watching
612 if (status == Powerd.Off) {
613 edgeDemo.paused = true;
614 } else if (status == Powerd.On) {
615 edgeDemo.paused = false;
616 }
617 }
618 }
620 function showHome() {
621 var animate = !greeter.shown && !stages.shown
622 greeter.hide()
623 dash.setCurrentScope("clickscope", animate, false)
624 stages.hide()
625 }
627 Item {
628 id: overlay
630 anchors.fill: parent
632 Panel {
633 id: panel
634 anchors.fill: parent //because this draws indicator menus
635 indicators {
636 hides: [launcher]
637 available: edgeDemo.panelEnabled
638 contentEnabled: edgeDemo.panelContentEnabled
639 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
640 panelHeight: units.gu(3)
641 }
642 property string focusedAppId: ApplicationManager.focusedApplicationId
643 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
644 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
646 InputFilterArea {
647 anchors {
648 top: parent.top
649 left: parent.left
650 right: parent.right
651 }
652 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
653 blockInput: true
654 }
655 }
657 InputFilterArea {
658 blockInput: launcher.shown
659 anchors {
660 top: parent.top
661 bottom: parent.bottom
662 left: parent.left
663 }
664 width: launcher.width
665 }
667 Launcher {
668 id: launcher
670 readonly property bool dashSwipe: progress > 0
672 anchors.top: parent.top
673 anchors.bottom: parent.bottom
674 width: parent.width
675 dragAreaWidth: shell.edgeSize
676 available: edgeDemo.launcherEnabled
678 onShowDashHome: {
679 if (edgeDemo.running)
680 return;
682 showHome()
683 }
684 onDash: {
685 if (stages.shown && !stages.overlayMode && !stages.locked) {
686 stages.hide();
687 launcher.fadeOut();
688 } else {
689 launcher.switchToNextState("visible");
690 }
692 if (greeter.shown) {
693 greeter.hideRight();
694 launcher.fadeOut();
695 }
696 }
697 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
698 onLauncherApplicationSelected: {
699 if (!edgeDemo.running)
700 shell.activateApplication(appId)
701 }
702 onShownChanged: {
703 if (shown) {
704 panel.indicators.hide()
705 }
706 }
707 }
709 Rectangle {
710 id: modalNotificationBackground
712 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
713 color: "#000000"
714 anchors.fill: parent
715 opacity: 0.5
717 MouseArea {
718 anchors.fill: parent
719 }
721 InputFilterArea {
722 anchors.fill: parent
723 blockInput: modalNotificationBackground.visible
724 }
725 }
727 Notifications {
728 id: notifications
730 model: NotificationBackend.Model
731 margin: units.gu(1)
733 y: panel.panelHeight
734 width: parent.width
735 height: parent.height - panel.panelHeight
737 states: [
738 State {
739 name: "narrow"
740 when: overlay.width <= units.gu(60)
741 AnchorChanges { target: notifications; anchors.left: parent.left }
742 },
743 State {
744 name: "wide"
745 when: overlay.width > units.gu(60)
746 AnchorChanges { target: notifications; anchors.left: undefined }
747 PropertyChanges { target: notifications; width: units.gu(38) }
748 }
749 ]
751 InputFilterArea {
752 anchors { left: parent.left; right: parent.right }
753 height: parent.contentHeight
754 blockInput: height > 0
755 }
756 }
757 }
759 focus: true
760 onFocusChanged: if (!focus) forceActiveFocus();
762 InputFilterArea {
763 anchors {
764 top: parent.top
765 bottom: parent.bottom
766 left: parent.left
767 }
768 width: shell.edgeSize
769 blockInput: true
770 }
772 InputFilterArea {
773 anchors {
774 top: parent.top
775 bottom: parent.bottom
776 right: parent.right
777 }
778 width: shell.edgeSize
779 blockInput: true
780 }
782 Binding {
783 target: i18n
784 property: "domain"
785 value: "unity8"
786 }
788 OSKController {
789 anchors.topMargin: panel.panelHeight
790 anchors.fill: parent // as needs to know the geometry of the shell
791 }
793 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
794 MouseArea {
795 anchors.bottom: parent.bottom
796 anchors.left: parent.left
797 anchors.right: parent.right
798 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
800 enabled: ApplicationManager.keyboardVisible
801 }
803 Label {
804 anchors.centerIn: parent
805 visible: ApplicationManager.fake ? ApplicationManager.fake : false
806 text: "EARLY ALPHA\nNOT READY FOR USE"
807 color: "lightgrey"
808 opacity: 0.2
809 font.weight: Font.Black
810 horizontalAlignment: Text.AlignHCenter
811 verticalAlignment: Text.AlignVCenter
812 fontSizeMode: Text.Fit
813 rotation: -45
814 scale: Math.min(parent.width, parent.height) / width
815 }
817 EdgeDemo {
818 id: edgeDemo
819 greeter: greeter
820 launcher: launcher
821 dash: dash
822 indicators: panel.indicators
823 underlay: underlay
824 }
826 Connections {
827 target: SessionBroadcast
828 onShowHome: showHome()
829 }
831 Keys.onPressed: {
832 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
833 if (!powerKeyTimer.running) {
834 powerKeyTimer.start();
835 }
836 event.accepted = true;
837 }
838 }
840 Keys.onReleased: {
841 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
842 powerKeyTimer.stop();
843 event.accepted = true;
844 }
845 }
847 Timer {
848 id: powerKeyTimer
849 interval: 2000
850 repeat: false
851 triggeredOnStart: false
853 onTriggered: {
854 stages.showPowerDialog();
855 }
856 }
858 Rectangle {
859 id: shutdownFadeOutRectangle
860 enabled: false
861 visible: false
862 color: "black"
863 anchors.fill: parent
864 opacity: 0.0
865 NumberAnimation on opacity {
866 id: shutdownFadeOut
867 from: 0.0
868 to: 1.0
869 onStopped: {
870 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
871 DBusUnitySessionService.Shutdown();
872 }
873 }
874 }
875 }