on dbe1eb8 help sign in
unity8/qml/Shell.qml
705 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 Unity.Launcher 0.1
24import Utils 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 "Stages"
35import Unity.Notifications 1.0 as NotificationBackend
36import Unity.Session 0.1
38Item {
39 id: shell
41 // this is only here to select the width / height of the window if not running fullscreen
42 property bool tablet: false
43 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
44 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
46 property real edgeSize: units.gu(2)
47 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
48 property url background
49 readonly property real panelHeight: panel.panelHeight
51 property bool dashShown: dash.shown && dash.available && underlay.visible
53 property bool sideStageEnabled: shell.width >= units.gu(100)
54 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
56 function activateApplication(appId) {
57 if (ApplicationManager.findApplication(appId)) {
58 ApplicationManager.requestFocusApplication(appId);
59 } else {
60 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
61 ApplicationManager.startApplication(appId, execFlags);
62 stages.show();
63 }
64 }
66 Binding {
67 target: LauncherModel
68 property: "applicationManager"
69 value: ApplicationManager
70 }
72 Component.onCompleted: {
73 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
74 }
76 GSettings {
77 id: backgroundSettings
78 schema.id: "org.gnome.desktop.background"
79 }
80 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
81 onGSettingsPictureChanged: {
82 shell.background = gSettingsPicture
83 }
85 VolumeControl {
86 id: volumeControl
87 }
89 WindowKeysFilter {
90 // Handle but do not filter out volume keys
91 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
92 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
94 Keys.onPressed: {
95 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
96 dialogs.onPowerKeyPressed();
97 event.accepted = true;
98 } else {
99 event.accepted = false;
100 }
101 }
103 Keys.onReleased: {
104 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
105 dialogs.onPowerKeyReleased();
106 event.accepted = true;
107 } else {
108 event.accepted = false;
109 }
110 }
111 }
113 Item {
114 id: underlay
115 objectName: "underlay"
116 anchors.fill: parent
118 // Whether the underlay is fully covered by opaque UI elements.
119 property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
120 || stages.fullyShown || greeterWrapper.fullyShown
121 visible: !fullyCovered
123 Dash {
124 id: dash
125 objectName: "dash"
127 available: !LightDM.Greeter.active
128 hides: [stages, launcher, panel.indicators]
129 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
130 !(panel.indicators.fullyOpened && !sideStageEnabled)
131 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
133 overviewHandleHeight: shell.edgeSize
135 anchors {
136 fill: parent
137 topMargin: panel.panelHeight
138 }
140 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
141 opacity: 1.0 - disappearingAnimationProgress
142 property real disappearingAnimationProgress: stages.showProgress
144 // FIXME: only necessary because stages.showProgress is not animated
145 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
146 }
147 }
149 EdgeDragArea {
150 id: stagesDragArea
151 direction: Direction.Leftwards
153 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
154 width: shell.edgeSize
156 property real progress: stages.width
158 onTouchXChanged: {
159 if (status == DirectionalDragArea.Recognized) {
160 if (ApplicationManager.empty) {
161 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
162 } else {
163 progress = stages.width - stagesDragArea.width + touchX;
164 }
165 }
166 }
168 onDraggingChanged: {
169 if (!dragging) {
170 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
171 stages.show();
172 }
173 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
174 }
175 }
176 }
178 Item {
179 id: stages
180 objectName: "stages"
181 width: parent.width
182 height: parent.height
184 visible: !fullyHidden && !ApplicationManager.empty
186 x: {
187 if (shown) {
188 if (locked || greeter.fakeActiveForApp !== "") {
189 return 0;
190 }
191 return launcher.progress;
192 } else {
193 return stagesDragArea.progress
194 }
195 }
196 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
198 property bool shown: false
199 onShownChanged: {
200 if (shown) {
201 if (ApplicationManager.count > 0) {
202 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
203 }
204 } else {
205 if (ApplicationManager.focusedApplicationId) {
206 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
207 ApplicationManager.unfocusCurrentApplication();
208 }
209 }
210 }
212 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
213 // zero. That breaks the property binding and the function won't be reevaluated once
214 // shell.width is set, with the NaN result staying there for good.
215 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
217 property bool fullyShown: x == 0
218 property bool fullyHidden: x == width
220 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
222 // It might technically not be fullyShown but visually it just looks so.
223 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
225 function show() {
226 shown = true;
227 }
229 function hide() {
230 shown = false;
231 }
233 Connections {
234 target: ApplicationManager
235 onFocusRequested: {
236 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
237 lockscreen.show();
238 }
239 greeter.hide();
240 stages.show();
241 }
243 onFocusedApplicationIdChanged: {
244 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
245 lockscreen.show();
246 }
247 panel.indicators.hide();
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 Loader {
267 id: applicationsDisplayLoader
268 anchors.fill: parent
270 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
272 Binding {
273 target: applicationsDisplayLoader.item
274 property: "objectName"
275 value: "stage"
276 }
277 Binding {
278 target: applicationsDisplayLoader.item
279 property: "dragAreaWidth"
280 value: shell.edgeSize
281 }
282 Binding {
283 target: applicationsDisplayLoader.item
284 property: "maximizedAppTopMargin"
285 // Not just using panel.panelHeight as that changes depending on the focused app.
286 value: panel.indicators.panelHeight
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "interactive"
291 value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
292 && panel.indicators.fullyClosed
293 }
294 Binding {
295 target: applicationsDisplayLoader.item
296 property: "spreadEnabled"
297 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
298 }
299 }
300 }
302 InputMethod {
303 id: inputMethod
304 objectName: "inputMethod"
305 anchors { fill: parent; topMargin: panel.panelHeight }
306 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
307 }
309 Connections {
310 target: SurfaceManager
311 onSurfaceCreated: {
312 if (surface.type == MirSurfaceItem.InputMethod) {
313 inputMethod.surface = surface;
314 }
315 }
317 onSurfaceDestroyed: {
318 if (inputMethod.surface == surface) {
319 inputMethod.surface = null;
320 surface.parent = null;
321 }
322 if (!surface.parent) {
323 // there's no one displaying it. delete it right away
324 surface.release();
325 }
326 }
327 }
329 Lockscreen {
330 id: lockscreen
331 objectName: "lockscreen"
333 readonly property int backgroundTopMargin: -panel.panelHeight
335 hides: [launcher, panel.indicators]
336 shown: false
337 enabled: true
338 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
339 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
340 y: panel.panelHeight
341 visible: required
342 width: parent.width
343 height: parent.height - panel.panelHeight
344 background: shell.background
345 minPinLength: 4
346 maxPinLength: 4
348 onEntered: LightDM.Greeter.respond(passphrase);
349 onCancel: greeter.show()
350 onEmergencyCall: {
351 greeter.fakeActiveForApp = "dialer-app"
352 shell.activateApplication("dialer-app")
353 lockscreen.hide()
354 }
356 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
358 Component.onCompleted: {
359 if (LightDM.Users.count == 1) {
360 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
361 }
362 }
363 }
365 Connections {
366 target: LightDM.Greeter
368 onShowGreeter: greeter.show()
370 onShowPrompt: {
371 if (LightDM.Users.count == 1) {
372 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
373 if (text == "PIN") {
374 lockscreen.alphaNumeric = false
375 } else {
376 lockscreen.alphaNumeric = true
377 }
378 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
379 lockscreen.show();
380 }
381 }
383 onAuthenticationComplete: {
384 if (LightDM.Greeter.promptless) {
385 return;
386 }
387 if (LightDM.Greeter.authenticated) {
388 lockscreen.hide();
389 greeter.login();
390 } else {
391 lockscreen.clear(true);
392 }
393 }
394 }
396 Binding {
397 target: LightDM.Greeter
398 property: "active"
399 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
400 }
402 Rectangle {
403 anchors.fill: parent
404 color: "black"
405 opacity: greeterWrapper.showProgress * 0.8
406 }
408 Item {
409 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
410 id: greeterWrapper
411 x: launcher.progress
412 y: panel.panelHeight
413 width: parent.width
414 height: parent.height - panel.panelHeight
416 Behavior on x {
417 enabled: !launcher.dashSwipe
418 StandardAnimation {}
419 }
421 property bool fullyShown: showProgress === 1.0
422 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
423 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
425 Greeter {
426 id: greeter
427 objectName: "greeter"
429 signal sessionStarted() // helpful for tests
431 property string fakeActiveForApp: ""
433 available: true
434 hides: [launcher, panel.indicators]
435 shown: true
437 defaultBackground: shell.background
439 width: parent.width
440 height: parent.height
442 dragHandleWidth: shell.edgeSize
444 function login() {
445 enabled = false;
446 LightDM.Greeter.startSessionSync();
447 sessionStarted();
448 greeter.hide();
449 lockscreen.hide();
450 launcher.hide();
451 enabled = true;
452 }
454 onShownChanged: {
455 if (shown) {
456 lockscreen.reset();
457 if (!LightDM.Greeter.promptless) {
458 lockscreen.show();
459 }
460 // If there is only one user, we start authenticating with that one here.
461 // If there are more users, the Greeter will handle that
462 if (LightDM.Users.count == 1) {
463 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
464 }
465 greeter.fakeActiveForApp = "";
466 greeter.forceActiveFocus();
467 }
468 }
470 onUnlocked: greeter.hide()
471 onSelected: {
472 // Update launcher items for new user
473 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
474 AccountsService.user = user;
475 LauncherModel.setUser(user);
476 }
478 onTease: launcher.tease()
480 Binding {
481 target: ApplicationManager
482 property: "suspended"
483 value: greeter.shown && greeterWrapper.showProgress == 1
484 }
485 }
486 }
488 Connections {
489 id: powerConnection
490 target: Powerd
492 onDisplayPowerStateChange: {
493 // We ignore any display-off signals when the proximity sensor
494 // is active. This usually indicates something like a phone call.
495 if (status == Powerd.Off && reason != Powerd.Proximity) {
496 greeter.showNow();
497 }
499 // No reason to chew demo CPU when user isn't watching
500 if (status == Powerd.Off) {
501 edgeDemo.paused = true;
502 } else if (status == Powerd.On) {
503 edgeDemo.paused = false;
504 }
505 }
506 }
508 function showHome() {
509 if (edgeDemo.running) {
510 return
511 }
513 if (LightDM.Greeter.active) {
514 if (!LightDM.Greeter.promptless) {
515 lockscreen.show()
516 }
517 greeter.hide()
518 }
520 var animate = !LightDM.Greeter.active && !stages.shown
521 dash.setCurrentScope("clickscope", animate, false)
522 stages.hide()
523 }
525 function showDash() {
526 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
527 return;
528 }
530 if (!stages.locked) {
531 stages.hide();
532 launcher.fadeOut();
533 } else {
534 launcher.switchToNextState("visible");
535 }
537 if (greeter.shown) {
538 greeter.hideRight();
539 launcher.fadeOut();
540 }
541 }
543 Item {
544 id: overlay
545 z: 10
547 anchors.fill: parent
549 Panel {
550 id: panel
551 objectName: "panel"
552 anchors.fill: parent //because this draws indicator menus
553 indicators {
554 hides: [launcher]
555 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
556 contentEnabled: edgeDemo.panelContentEnabled
557 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
558 panelHeight: units.gu(3)
559 }
561 property bool topmostApplicationIsFullscreen:
562 ApplicationManager.focusedApplicationId &&
563 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
565 fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
566 && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
567 }
569 Launcher {
570 id: launcher
571 objectName: "launcher"
573 readonly property bool dashSwipe: progress > 0
575 anchors.top: parent.top
576 anchors.bottom: parent.bottom
577 width: parent.width
578 dragAreaWidth: shell.edgeSize
579 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
581 onShowDashHome: showHome()
582 onDash: showDash()
583 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
584 onLauncherApplicationSelected: {
585 if (greeter.fakeActiveForApp !== "") {
586 lockscreen.show()
587 }
588 if (!edgeDemo.running)
589 shell.activateApplication(appId)
590 }
591 onShownChanged: {
592 if (shown) {
593 panel.indicators.hide()
594 }
595 }
596 }
598 Rectangle {
599 id: modalNotificationBackground
601 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
602 color: "#000000"
603 anchors.fill: parent
604 opacity: 0.5
606 MouseArea {
607 anchors.fill: parent
608 }
609 }
611 Notifications {
612 id: notifications
614 model: NotificationBackend.Model
615 margin: units.gu(1)
617 y: panel.panelHeight
618 width: parent.width
619 height: parent.height - panel.panelHeight
621 states: [
622 State {
623 name: "narrow"
624 when: overlay.width <= units.gu(60)
625 AnchorChanges { target: notifications; anchors.left: parent.left }
626 },
627 State {
628 name: "wide"
629 when: overlay.width > units.gu(60)
630 AnchorChanges { target: notifications; anchors.left: undefined }
631 PropertyChanges { target: notifications; width: units.gu(38) }
632 }
633 ]
634 }
635 }
637 Binding {
638 target: i18n
639 property: "domain"
640 value: "unity8"
641 }
643 Dialogs {
644 id: dialogs
645 anchors.fill: parent
646 z: overlay.z + 10
647 onPowerOffClicked: {
648 shutdownFadeOutRectangle.enabled = true;
649 shutdownFadeOutRectangle.visible = true;
650 shutdownFadeOut.start();
651 }
652 }
654 Label {
655 id: alphaDisclaimerLabel
656 anchors.centerIn: parent
657 visible: ApplicationManager.fake ? ApplicationManager.fake : false
658 z: dialogs.z + 10
659 text: "EARLY ALPHA\nNOT READY FOR USE"
660 color: "lightgrey"
661 opacity: 0.2
662 font.weight: Font.Black
663 horizontalAlignment: Text.AlignHCenter
664 verticalAlignment: Text.AlignVCenter
665 fontSizeMode: Text.Fit
666 rotation: -45
667 scale: Math.min(parent.width, parent.height) / width
668 }
670 EdgeDemo {
671 id: edgeDemo
672 z: alphaDisclaimerLabel.z + 10
673 greeter: greeter
674 launcher: launcher
675 dash: dash
676 indicators: panel.indicators
677 underlay: underlay
678 }
680 Connections {
681 target: SessionBroadcast
682 onShowHome: showHome()
683 }
685 Rectangle {
686 id: shutdownFadeOutRectangle
687 z: edgeDemo.z + 10
688 enabled: false
689 visible: false
690 color: "black"
691 anchors.fill: parent
692 opacity: 0.0
693 NumberAnimation on opacity {
694 id: shutdownFadeOut
695 from: 0.0
696 to: 1.0
697 onStopped: {
698 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
699 DBusUnitySessionService.Shutdown();
700 }
701 }
702 }
703 }