on 07147a1 help sign in
unity8/qml/Shell.qml
726 lines 23 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 Ubuntu.Telephony 0.1 as Telephony
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 dialogs.onPowerKeyPressed();
98 event.accepted = true;
99 } else {
100 event.accepted = false;
101 }
102 }
104 Keys.onReleased: {
105 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
106 dialogs.onPowerKeyReleased();
107 event.accepted = true;
108 } else {
109 event.accepted = false;
110 }
111 }
112 }
114 Item {
115 id: underlay
116 objectName: "underlay"
117 anchors.fill: parent
119 // Whether the underlay is fully covered by opaque UI elements.
120 property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
121 || stages.fullyShown || greeterWrapper.fullyShown
122 visible: !fullyCovered
124 Image {
125 anchors.fill: dash
126 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
127 fillMode: Image.PreserveAspectCrop
128 horizontalAlignment: Image.AlignRight
129 verticalAlignment: Image.AlignTop
130 }
132 Dash {
133 id: dash
134 objectName: "dash"
136 available: !LightDM.Greeter.active
137 hides: [stages, launcher, panel.indicators]
138 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
139 !(panel.indicators.fullyOpened && !sideStageEnabled)
140 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
142 anchors {
143 fill: parent
144 topMargin: panel.panelHeight
145 }
147 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
148 opacity: 1.0 - disappearingAnimationProgress
149 property real disappearingAnimationProgress: stages.showProgress
151 // FIXME: only necessary because stages.showProgress is not animated
152 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
153 }
154 }
156 EdgeDragArea {
157 id: stagesDragArea
158 direction: Direction.Leftwards
160 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
161 width: shell.edgeSize
163 property real progress: stages.width
165 onTouchXChanged: {
166 if (status == DirectionalDragArea.Recognized) {
167 if (ApplicationManager.empty) {
168 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
169 } else {
170 progress = stages.width - stagesDragArea.width + touchX;
171 }
172 }
173 }
175 onDraggingChanged: {
176 if (!dragging) {
177 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
178 stages.show();
179 }
180 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
181 }
182 }
183 }
185 Item {
186 id: stages
187 objectName: "stages"
188 width: parent.width
189 height: parent.height
191 visible: !fullyHidden && !ApplicationManager.empty
193 x: {
194 if (shown) {
195 if (locked || greeter.fakeActiveForApp !== "") {
196 return 0;
197 }
198 return launcher.progress;
199 } else {
200 return stagesDragArea.progress
201 }
202 }
203 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
205 property bool shown: false
206 onShownChanged: {
207 if (shown) {
208 if (ApplicationManager.count > 0) {
209 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
210 }
211 } else {
212 if (ApplicationManager.focusedApplicationId) {
213 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
214 ApplicationManager.unfocusCurrentApplication();
215 }
216 }
217 }
219 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
220 // zero. That breaks the property binding and the function won't be reevaluated once
221 // shell.width is set, with the NaN result staying there for good.
222 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
224 property bool fullyShown: x == 0
225 property bool fullyHidden: x == width
227 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
229 // It might technically not be fullyShown but visually it just looks so.
230 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
232 function show() {
233 shown = true;
234 }
236 function hide() {
237 shown = false;
238 }
240 Connections {
241 target: ApplicationManager
242 onFocusRequested: {
243 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
244 lockscreen.show();
245 }
246 greeter.hide();
247 stages.show();
248 }
250 onFocusedApplicationIdChanged: {
251 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
252 lockscreen.show();
253 }
254 panel.indicators.hide();
255 }
257 onApplicationAdded: {
258 if (greeter.shown) {
259 greeter.hide();
260 }
261 if (!stages.shown) {
262 stages.show();
263 }
264 }
266 onEmptyChanged: {
267 if (ApplicationManager.empty) {
268 stages.hide();
269 }
270 }
271 }
273 Loader {
274 id: applicationsDisplayLoader
275 anchors.fill: parent
277 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
279 Binding {
280 target: applicationsDisplayLoader.item
281 property: "objectName"
282 value: "stage"
283 }
284 Binding {
285 target: applicationsDisplayLoader.item
286 property: "dragAreaWidth"
287 value: shell.edgeSize
288 }
289 Binding {
290 target: applicationsDisplayLoader.item
291 property: "maximizedAppTopMargin"
292 // Not just using panel.panelHeight as that changes depending on the focused app.
293 value: panel.indicators.panelHeight
294 }
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "interactive"
298 value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
299 && panel.indicators.fullyClosed
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "spreadEnabled"
304 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
305 }
306 }
307 }
309 InputMethod {
310 id: inputMethod
311 objectName: "inputMethod"
312 anchors { fill: parent; topMargin: panel.panelHeight }
313 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
314 }
316 Connections {
317 target: SurfaceManager
318 onSurfaceCreated: {
319 if (surface.type == MirSurfaceItem.InputMethod) {
320 inputMethod.surface = surface;
321 }
322 }
324 onSurfaceDestroyed: {
325 if (inputMethod.surface == surface) {
326 inputMethod.surface = null;
327 surface.parent = null;
328 }
329 if (!surface.parent) {
330 // there's no one displaying it. delete it right away
331 surface.release();
332 }
333 }
334 }
336 Lockscreen {
337 id: lockscreen
338 objectName: "lockscreen"
340 readonly property int backgroundTopMargin: -panel.panelHeight
342 hides: [launcher, panel.indicators]
343 shown: false
344 enabled: true
345 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
346 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
347 y: panel.panelHeight
348 visible: required
349 width: parent.width
350 height: parent.height - panel.panelHeight
351 background: shell.background
352 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
353 minPinLength: 4
354 maxPinLength: 4
356 onEntered: LightDM.Greeter.respond(passphrase);
357 onCancel: greeter.show()
358 onEmergencyCall: {
359 greeter.fakeActiveForApp = "dialer-app"
360 shell.activateApplication("dialer-app")
361 lockscreen.hide()
362 }
364 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
366 Component.onCompleted: {
367 if (greeter.narrowMode) {
368 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
369 }
370 }
371 }
373 Connections {
374 target: LightDM.Greeter
376 onShowGreeter: greeter.show()
378 onShowPrompt: {
379 if (greeter.narrowMode) {
380 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
381 lockscreen.show();
382 }
383 }
385 onPromptlessChanged: {
386 if (LightDM.Greeter.promptless) {
387 lockscreen.hide()
388 } else {
389 lockscreen.reset();
390 lockscreen.show();
391 }
392 }
394 onAuthenticationComplete: {
395 if (LightDM.Greeter.promptless) {
396 return;
397 }
398 if (LightDM.Greeter.authenticated) {
399 lockscreen.hide();
400 greeter.login();
401 } else {
402 lockscreen.clear(true);
403 if (greeter.narrowMode) {
404 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
405 }
406 }
407 }
408 }
410 Binding {
411 target: LightDM.Greeter
412 property: "active"
413 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
414 }
416 Rectangle {
417 anchors.fill: parent
418 color: "black"
419 opacity: greeterWrapper.showProgress * 0.8
420 }
422 Item {
423 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
424 id: greeterWrapper
425 x: launcher.progress
426 y: panel.panelHeight
427 width: parent.width
428 height: parent.height - panel.panelHeight
430 Behavior on x {
431 enabled: !launcher.dashSwipe
432 StandardAnimation {}
433 }
435 property bool fullyShown: showProgress === 1.0
436 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
437 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
439 Greeter {
440 id: greeter
441 objectName: "greeter"
443 signal sessionStarted() // helpful for tests
445 property string fakeActiveForApp: ""
447 available: true
448 hides: [launcher, panel.indicators]
449 shown: true
451 defaultBackground: shell.background
453 width: parent.width
454 height: parent.height
456 dragHandleWidth: shell.edgeSize
458 function login() {
459 enabled = false;
460 if (LightDM.Greeter.startSessionSync()) {
461 sessionStarted();
462 greeter.hide();
463 lockscreen.hide();
464 launcher.hide();
465 }
466 enabled = true;
467 }
469 onShownChanged: {
470 if (shown) {
471 if (greeter.narrowMode) {
472 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
473 }
474 if (!LightDM.Greeter.promptless) {
475 lockscreen.reset();
476 lockscreen.show();
477 }
478 greeter.fakeActiveForApp = "";
479 greeter.forceActiveFocus();
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 onTease: launcher.tease()
493 Binding {
494 target: ApplicationManager
495 property: "suspended"
496 value: greeter.shown && greeterWrapper.showProgress == 1
497 }
498 }
499 }
501 function showGreeterIfScreenIsOff() {
502 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
503 greeter.showNow()
504 }
505 }
507 Connections {
508 id: callConnection
509 target: callManager
510 onHasCallsChanged: showGreeterIfScreenIsOff()
511 }
513 Connections {
514 id: powerConnection
515 target: Powerd
517 onStatusChanged: {
518 showGreeterIfScreenIsOff()
520 // No reason to chew demo CPU when user isn't watching
521 if (Powerd.status === Powerd.Off) {
522 edgeDemo.paused = true;
523 } else if (Powerd.status === Powerd.On) {
524 edgeDemo.paused = false;
525 }
526 }
527 }
529 function showHome() {
530 if (edgeDemo.running) {
531 return
532 }
534 if (LightDM.Greeter.active) {
535 if (!LightDM.Greeter.promptless) {
536 lockscreen.show()
537 }
538 greeter.hide()
539 }
541 var animate = !LightDM.Greeter.active && !stages.shown
542 dash.setCurrentScope("clickscope", animate, false)
543 stages.hide()
544 }
546 function showDash() {
547 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
548 return;
549 }
551 if (!stages.locked) {
552 stages.hide();
553 launcher.fadeOut();
554 } else {
555 launcher.switchToNextState("visible");
556 }
558 if (greeter.shown) {
559 greeter.hideRight();
560 launcher.fadeOut();
561 }
562 }
564 Item {
565 id: overlay
566 z: 10
568 anchors.fill: parent
570 Panel {
571 id: panel
572 objectName: "panel"
573 anchors.fill: parent //because this draws indicator menus
574 indicators {
575 hides: [launcher]
576 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
577 contentEnabled: edgeDemo.panelContentEnabled
578 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
579 panelHeight: units.gu(3)
580 }
582 property bool topmostApplicationIsFullscreen:
583 ApplicationManager.focusedApplicationId &&
584 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
586 fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
587 && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
588 }
590 Launcher {
591 id: launcher
592 objectName: "launcher"
594 readonly property bool dashSwipe: progress > 0
596 anchors.top: parent.top
597 anchors.bottom: parent.bottom
598 width: parent.width
599 dragAreaWidth: shell.edgeSize
600 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
602 onShowDashHome: showHome()
603 onDash: showDash()
604 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
605 onLauncherApplicationSelected: {
606 if (greeter.fakeActiveForApp !== "") {
607 lockscreen.show()
608 }
609 if (!edgeDemo.running)
610 shell.activateApplication(appId)
611 }
612 onShownChanged: {
613 if (shown) {
614 panel.indicators.hide()
615 }
616 }
617 }
619 Rectangle {
620 id: modalNotificationBackground
622 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
623 color: "#000000"
624 anchors.fill: parent
625 opacity: 0.5
627 MouseArea {
628 anchors.fill: parent
629 }
630 }
632 Notifications {
633 id: notifications
635 model: NotificationBackend.Model
636 margin: units.gu(1)
638 y: panel.panelHeight
639 width: parent.width
640 height: parent.height - panel.panelHeight
642 states: [
643 State {
644 name: "narrow"
645 when: overlay.width <= units.gu(60)
646 AnchorChanges { target: notifications; anchors.left: parent.left }
647 },
648 State {
649 name: "wide"
650 when: overlay.width > units.gu(60)
651 AnchorChanges { target: notifications; anchors.left: undefined }
652 PropertyChanges { target: notifications; width: units.gu(38) }
653 }
654 ]
655 }
656 }
658 Binding {
659 target: i18n
660 property: "domain"
661 value: "unity8"
662 }
664 Dialogs {
665 id: dialogs
666 anchors.fill: parent
667 z: overlay.z + 10
668 onPowerOffClicked: {
669 shutdownFadeOutRectangle.enabled = true;
670 shutdownFadeOutRectangle.visible = true;
671 shutdownFadeOut.start();
672 }
673 }
675 Label {
676 id: alphaDisclaimerLabel
677 anchors.centerIn: parent
678 visible: ApplicationManager.fake ? ApplicationManager.fake : false
679 z: dialogs.z + 10
680 text: "EARLY ALPHA\nNOT READY FOR USE"
681 color: "lightgrey"
682 opacity: 0.2
683 font.weight: Font.Black
684 horizontalAlignment: Text.AlignHCenter
685 verticalAlignment: Text.AlignVCenter
686 fontSizeMode: Text.Fit
687 rotation: -45
688 scale: Math.min(parent.width, parent.height) / width
689 }
691 EdgeDemo {
692 id: edgeDemo
693 z: alphaDisclaimerLabel.z + 10
694 greeter: greeter
695 launcher: launcher
696 dash: dash
697 indicators: panel.indicators
698 underlay: underlay
699 }
701 Connections {
702 target: SessionBroadcast
703 onShowHome: showHome()
704 }
706 Rectangle {
707 id: shutdownFadeOutRectangle
708 z: edgeDemo.z + 10
709 enabled: false
710 visible: false
711 color: "black"
712 anchors.fill: parent
713 opacity: 0.0
714 NumberAnimation on opacity {
715 id: shutdownFadeOut
716 from: 0.0
717 to: 1.0
718 onStopped: {
719 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
720 DBusUnitySessionService.Shutdown();
721 }
722 }
723 }
724 }