on edc0cca help sign in
unity8/qml/Shell.qml
814 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.Components.Popups 0.1
23import Ubuntu.Gestures 0.1
24import Unity.Launcher 0.1
25import Utils 0.1
26import LightDM 0.1 as LightDM
27import Powerd 0.1
28import SessionBroadcast 0.1
29import "Dash"
30import "Greeter"
31import "Launcher"
32import "Panel"
33import "Components"
34import "Notifications"
35import "Stages"
36import Unity.Notifications 1.0 as NotificationBackend
37import Unity.Session 0.1
39Item {
40 id: shell
42 // this is only here to select the width / height of the window if not running fullscreen
43 property bool tablet: false
44 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
45 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
47 property real edgeSize: units.gu(2)
48 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
49 property url background
50 readonly property real panelHeight: panel.panelHeight
52 property bool dashShown: dash.shown && dash.available && underlay.visible
54 property bool sideStageEnabled: shell.width >= units.gu(100)
55 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
57 function activateApplication(appId) {
58 if (ApplicationManager.findApplication(appId)) {
59 ApplicationManager.requestFocusApplication(appId);
60 } else {
61 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62 ApplicationManager.startApplication(appId, execFlags);
63 stages.show();
64 }
65 }
67 Binding {
68 target: LauncherModel
69 property: "applicationManager"
70 value: ApplicationManager
71 }
73 Component.onCompleted: {
74 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75 }
77 GSettings {
78 id: backgroundSettings
79 schema.id: "org.gnome.desktop.background"
80 }
81 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
82 onGSettingsPictureChanged: {
83 shell.background = gSettingsPicture
84 }
86 VolumeControl {
87 id: volumeControl
88 }
90 WindowKeysFilter {
91 // Handle but do not filter out volume keys
92 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
93 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
95 Keys.onPressed: {
96 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
97 if (!powerKeyTimer.running) {
98 powerKeyTimer.start();
99 }
100 event.accepted = true;
101 } else {
102 event.accepted = false;
103 }
104 }
106 Keys.onReleased: {
107 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
108 powerKeyTimer.stop();
109 event.accepted = true;
110 } else {
111 event.accepted = false;
112 }
113 }
114 }
116 Item {
117 id: underlay
118 objectName: "underlay"
119 anchors.fill: parent
121 // Whether the underlay is fully covered by opaque UI elements.
122 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
123 visible: !fullyCovered
125 Image {
126 anchors.fill: dash
127 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
128 fillMode: Image.PreserveAspectCrop
129 horizontalAlignment: Image.AlignRight
130 verticalAlignment: Image.AlignTop
131 }
133 Dash {
134 id: dash
135 objectName: "dash"
137 available: !greeter.shown && !lockscreen.shown
138 hides: [stages, launcher, panel.indicators]
139 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
140 !(panel.indicators.fullyOpened && !sideStageEnabled)
141 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
143 anchors {
144 fill: parent
145 topMargin: panel.panelHeight
146 }
148 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
149 opacity: 1.0 - disappearingAnimationProgress
150 property real disappearingAnimationProgress: stages.showProgress
152 // FIXME: only necessary because stages.showProgress is not animated
153 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
154 }
155 }
157 EdgeDragArea {
158 id: stagesDragArea
159 direction: Direction.Leftwards
161 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
162 width: shell.edgeSize
164 property real progress: stages.width
166 onTouchXChanged: {
167 if (status == DirectionalDragArea.Recognized) {
168 if (ApplicationManager.empty) {
169 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
170 } else {
171 progress = stages.width - stagesDragArea.width + touchX;
172 }
173 }
174 }
176 onDraggingChanged: {
177 if (!dragging) {
178 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
179 stages.show();
180 }
181 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
182 }
183 }
184 }
186 Item {
187 id: stages
188 objectName: "stages"
189 width: parent.width
190 height: parent.height
192 visible: !fullyHidden && !ApplicationManager.empty
194 x: {
195 if (shown) {
196 if (locked) {
197 return 0;
198 }
199 return launcher.progress;
200 } else {
201 return stagesDragArea.progress
202 }
203 }
204 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
206 property bool shown: false
207 onShownChanged: {
208 if (shown) {
209 if (ApplicationManager.count > 0) {
210 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
211 }
212 } else {
213 if (ApplicationManager.focusedApplicationId) {
214 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
215 ApplicationManager.unfocusCurrentApplication();
216 }
217 }
218 }
220 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
221 // zero. That breaks the property binding and the function won't be reevaluated once
222 // shell.width is set, with the NaN result staying there for good.
223 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
225 property bool fullyShown: x == 0
226 property bool fullyHidden: x == width
228 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
230 // It might technically not be fullyShown but visually it just looks so.
231 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
233 function show() {
234 shown = true;
235 }
237 function hide() {
238 shown = false;
239 }
241 Connections {
242 target: ApplicationManager
243 onFocusRequested: {
244 if (greeter.shown) {
245 greeter.hide();
246 }
247 stages.show();
248 }
250 onApplicationAdded: {
251 if (greeter.shown) {
252 greeter.hide();
253 }
254 if (!stages.shown) {
255 stages.show();
256 }
257 }
259 onEmptyChanged: {
260 if (ApplicationManager.empty) {
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/TabletStage.qml" : "Stages/PhoneStage.qml"
436 Binding {
437 target: applicationsDisplayLoader.item
438 property: "dragAreaWidth"
439 value: shell.edgeSize
440 }
441 Binding {
442 target: applicationsDisplayLoader.item
443 property: "maximizedAppTopMargin"
444 // Not just using panel.panelHeight as that changes depending on the focused app.
445 value: panel.indicators.panelHeight
446 }
447 Binding {
448 target: applicationsDisplayLoader.item
449 property: "interactive"
450 value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
451 }
452 }
453 }
455 InputMethod {
456 id: inputMethod
457 anchors { fill: parent; topMargin: panel.panelHeight }
458 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
459 }
461 Connections {
462 target: SurfaceManager
463 onSurfaceCreated: {
464 if (surface.type == MirSurfaceItem.InputMethod) {
465 inputMethod.surface = surface;
466 }
467 }
469 onSurfaceDestroyed: {
470 if (inputMethod.surface == surface) {
471 inputMethod.surface = null;
472 surface.parent = null;
473 }
474 if (!surface.parent) {
475 // there's no one displaying it. delete it right away
476 surface.release();
477 }
478 }
479 }
481 Lockscreen {
482 id: lockscreen
483 objectName: "lockscreen"
485 readonly property int backgroundTopMargin: -panel.panelHeight
487 hides: [launcher, panel.indicators]
488 shown: false
489 enabled: true
490 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
491 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
492 y: panel.panelHeight
493 visible: required
494 width: parent.width
495 height: parent.height - panel.panelHeight
496 background: shell.background
497 minPinLength: 4
498 maxPinLength: 4
500 onEntered: LightDM.Greeter.respond(passphrase);
501 onCancel: greeter.show()
503 Component.onCompleted: {
504 if (LightDM.Users.count == 1) {
505 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
506 }
507 }
508 }
510 Connections {
511 target: LightDM.Greeter
513 onShowGreeter: greeter.show()
515 onShowPrompt: {
516 if (LightDM.Users.count == 1) {
517 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
518 if (text == "PIN") {
519 lockscreen.alphaNumeric = false
520 } else {
521 lockscreen.alphaNumeric = true
522 }
523 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
524 lockscreen.show();
525 }
526 }
528 onAuthenticationComplete: {
529 if (LightDM.Greeter.promptless) {
530 return;
531 }
532 if (LightDM.Greeter.authenticated) {
533 lockscreen.hide();
534 } else {
535 lockscreen.clear(true);
536 }
537 }
538 }
540 Binding {
541 target: LightDM.Greeter
542 property: "active"
543 value: greeter.shown || lockscreen.shown
544 }
546 Rectangle {
547 anchors.fill: parent
548 color: "black"
549 opacity: greeterWrapper.showProgress * 0.8
550 }
552 Item {
553 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
554 id: greeterWrapper
555 x: launcher.progress
556 y: panel.panelHeight
557 width: parent.width
558 height: parent.height - panel.panelHeight
560 Behavior on x {
561 enabled: !launcher.dashSwipe
562 StandardAnimation {}
563 }
565 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
567 Greeter {
568 id: greeter
569 objectName: "greeter"
571 available: true
572 hides: [launcher, panel.indicators]
573 shown: true
575 defaultBackground: shell.background
577 width: parent.width
578 height: parent.height
580 dragHandleWidth: shell.edgeSize
582 onShownChanged: {
583 if (shown) {
584 lockscreen.reset();
585 if (!LightDM.Greeter.promptless) {
586 lockscreen.show();
587 }
588 // If there is only one user, we start authenticating with that one here.
589 // If there are more users, the Greeter will handle that
590 if (LightDM.Users.count == 1) {
591 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
592 }
593 greeter.forceActiveFocus();
594 }
595 }
597 onUnlocked: greeter.hide()
598 onSelected: {
599 // Update launcher items for new user
600 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
601 AccountsService.user = user;
602 LauncherModel.setUser(user);
603 }
605 onTease: launcher.tease()
607 Binding {
608 target: ApplicationManager
609 property: "suspended"
610 value: greeter.shown && greeterWrapper.showProgress == 1
611 }
612 }
613 }
615 Connections {
616 id: powerConnection
617 target: Powerd
619 onDisplayPowerStateChange: {
620 // We ignore any display-off signals when the proximity sensor
621 // is active. This usually indicates something like a phone call.
622 if (status == Powerd.Off && reason != Powerd.Proximity) {
623 greeter.showNow();
624 }
626 // No reason to chew demo CPU when user isn't watching
627 if (status == Powerd.Off) {
628 edgeDemo.paused = true;
629 } else if (status == Powerd.On) {
630 edgeDemo.paused = false;
631 }
632 }
633 }
635 function showHome() {
636 var animate = !greeter.shown && !stages.shown
637 greeter.hide()
638 dash.setCurrentScope("clickscope", animate, false)
639 stages.hide()
640 }
642 Item {
643 id: overlay
644 z: 10
646 anchors.fill: parent
648 Panel {
649 id: panel
650 anchors.fill: parent //because this draws indicator menus
651 indicators {
652 hides: [launcher]
653 available: edgeDemo.panelEnabled
654 contentEnabled: edgeDemo.panelContentEnabled
655 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
656 panelHeight: units.gu(3)
657 }
659 property bool topmostApplicationIsFullscreen:
660 ApplicationManager.focusedApplicationId &&
661 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
663 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
664 && !greeter.shown && !lockscreen.shown
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.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 }
720 }
722 Notifications {
723 id: notifications
725 model: NotificationBackend.Model
726 margin: units.gu(1)
728 y: panel.panelHeight
729 width: parent.width
730 height: parent.height - panel.panelHeight
732 states: [
733 State {
734 name: "narrow"
735 when: overlay.width <= units.gu(60)
736 AnchorChanges { target: notifications; anchors.left: parent.left }
737 },
738 State {
739 name: "wide"
740 when: overlay.width > units.gu(60)
741 AnchorChanges { target: notifications; anchors.left: undefined }
742 PropertyChanges { target: notifications; width: units.gu(38) }
743 }
744 ]
745 }
746 }
748 Binding {
749 target: i18n
750 property: "domain"
751 value: "unity8"
752 }
754 Label {
755 id: alphaDisclaimerLabel
756 anchors.centerIn: parent
757 visible: ApplicationManager.fake ? ApplicationManager.fake : false
758 z: overlay.z + 10
759 text: "EARLY ALPHA\nNOT READY FOR USE"
760 color: "lightgrey"
761 opacity: 0.2
762 font.weight: Font.Black
763 horizontalAlignment: Text.AlignHCenter
764 verticalAlignment: Text.AlignVCenter
765 fontSizeMode: Text.Fit
766 rotation: -45
767 scale: Math.min(parent.width, parent.height) / width
768 }
770 EdgeDemo {
771 id: edgeDemo
772 z: alphaDisclaimerLabel.z + 10
773 greeter: greeter
774 launcher: launcher
775 dash: dash
776 indicators: panel.indicators
777 underlay: underlay
778 }
780 Connections {
781 target: SessionBroadcast
782 onShowHome: showHome()
783 }
785 Timer {
786 id: powerKeyTimer
787 interval: 2000
788 repeat: false
789 triggeredOnStart: false
791 onTriggered: {
792 stages.showPowerDialog();
793 }
794 }
796 Rectangle {
797 id: shutdownFadeOutRectangle
798 enabled: false
799 visible: false
800 color: "black"
801 anchors.fill: parent
802 opacity: 0.0
803 NumberAnimation on opacity {
804 id: shutdownFadeOut
805 from: 0.0
806 to: 1.0
807 onStopped: {
808 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
809 DBusUnitySessionService.Shutdown();
810 }
811 }
812 }
813 }