on b028843 help sign in
unity8/qml/Shell.qml
711 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 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
346 minPinLength: 4
347 maxPinLength: 4
349 onEntered: LightDM.Greeter.respond(passphrase);
350 onCancel: greeter.show()
351 onEmergencyCall: {
352 greeter.fakeActiveForApp = "dialer-app"
353 shell.activateApplication("dialer-app")
354 lockscreen.hide()
355 }
357 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
359 Component.onCompleted: {
360 if (greeter.narrowMode) {
361 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
362 }
363 }
364 }
366 Connections {
367 target: LightDM.Greeter
369 onShowGreeter: greeter.show()
371 onShowPrompt: {
372 if (greeter.narrowMode) {
373 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
374 lockscreen.show();
375 }
376 }
378 onPromptlessChanged: {
379 if (LightDM.Greeter.promptless) {
380 lockscreen.hide()
381 } else {
382 lockscreen.reset();
383 lockscreen.show();
384 }
385 }
387 onAuthenticationComplete: {
388 if (LightDM.Greeter.promptless) {
389 return;
390 }
391 if (LightDM.Greeter.authenticated) {
392 lockscreen.hide();
393 greeter.login();
394 } else {
395 lockscreen.clear(true);
396 if (greeter.narrowMode) {
397 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
398 }
399 }
400 }
401 }
403 Binding {
404 target: LightDM.Greeter
405 property: "active"
406 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
407 }
409 Rectangle {
410 anchors.fill: parent
411 color: "black"
412 opacity: greeterWrapper.showProgress * 0.8
413 }
415 Item {
416 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
417 id: greeterWrapper
418 x: launcher.progress
419 y: panel.panelHeight
420 width: parent.width
421 height: parent.height - panel.panelHeight
423 Behavior on x {
424 enabled: !launcher.dashSwipe
425 StandardAnimation {}
426 }
428 property bool fullyShown: showProgress === 1.0
429 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
430 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
432 Greeter {
433 id: greeter
434 objectName: "greeter"
436 signal sessionStarted() // helpful for tests
438 property string fakeActiveForApp: ""
440 available: true
441 hides: [launcher, panel.indicators]
442 shown: true
444 defaultBackground: shell.background
446 width: parent.width
447 height: parent.height
449 dragHandleWidth: shell.edgeSize
451 function login() {
452 enabled = false;
453 if (LightDM.Greeter.startSessionSync()) {
454 sessionStarted();
455 greeter.hide();
456 lockscreen.hide();
457 launcher.hide();
458 }
459 enabled = true;
460 }
462 onShownChanged: {
463 if (shown) {
464 if (greeter.narrowMode) {
465 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
466 }
467 if (!LightDM.Greeter.promptless) {
468 lockscreen.reset();
469 lockscreen.show();
470 }
471 greeter.fakeActiveForApp = "";
472 greeter.forceActiveFocus();
473 }
474 }
476 onUnlocked: greeter.hide()
477 onSelected: {
478 // Update launcher items for new user
479 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
480 AccountsService.user = user;
481 LauncherModel.setUser(user);
482 }
484 onTease: launcher.tease()
486 Binding {
487 target: ApplicationManager
488 property: "suspended"
489 value: greeter.shown && greeterWrapper.showProgress == 1
490 }
491 }
492 }
494 Connections {
495 id: powerConnection
496 target: Powerd
498 onDisplayPowerStateChange: {
499 // We ignore any display-off signals when the proximity sensor
500 // is active. This usually indicates something like a phone call.
501 if (status == Powerd.Off && reason != Powerd.Proximity) {
502 greeter.showNow();
503 }
505 // No reason to chew demo CPU when user isn't watching
506 if (status == Powerd.Off) {
507 edgeDemo.paused = true;
508 } else if (status == Powerd.On) {
509 edgeDemo.paused = false;
510 }
511 }
512 }
514 function showHome() {
515 if (edgeDemo.running) {
516 return
517 }
519 if (LightDM.Greeter.active) {
520 if (!LightDM.Greeter.promptless) {
521 lockscreen.show()
522 }
523 greeter.hide()
524 }
526 var animate = !LightDM.Greeter.active && !stages.shown
527 dash.setCurrentScope("clickscope", animate, false)
528 stages.hide()
529 }
531 function showDash() {
532 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
533 return;
534 }
536 if (!stages.locked) {
537 stages.hide();
538 launcher.fadeOut();
539 } else {
540 launcher.switchToNextState("visible");
541 }
543 if (greeter.shown) {
544 greeter.hideRight();
545 launcher.fadeOut();
546 }
547 }
549 Item {
550 id: overlay
551 z: 10
553 anchors.fill: parent
555 Panel {
556 id: panel
557 objectName: "panel"
558 anchors.fill: parent //because this draws indicator menus
559 indicators {
560 hides: [launcher]
561 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
562 contentEnabled: edgeDemo.panelContentEnabled
563 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
564 panelHeight: units.gu(3)
565 }
567 property bool topmostApplicationIsFullscreen:
568 ApplicationManager.focusedApplicationId &&
569 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
571 fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
572 && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
573 }
575 Launcher {
576 id: launcher
577 objectName: "launcher"
579 readonly property bool dashSwipe: progress > 0
581 anchors.top: parent.top
582 anchors.bottom: parent.bottom
583 width: parent.width
584 dragAreaWidth: shell.edgeSize
585 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
587 onShowDashHome: showHome()
588 onDash: showDash()
589 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
590 onLauncherApplicationSelected: {
591 if (greeter.fakeActiveForApp !== "") {
592 lockscreen.show()
593 }
594 if (!edgeDemo.running)
595 shell.activateApplication(appId)
596 }
597 onShownChanged: {
598 if (shown) {
599 panel.indicators.hide()
600 }
601 }
602 }
604 Rectangle {
605 id: modalNotificationBackground
607 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
608 color: "#000000"
609 anchors.fill: parent
610 opacity: 0.5
612 MouseArea {
613 anchors.fill: parent
614 }
615 }
617 Notifications {
618 id: notifications
620 model: NotificationBackend.Model
621 margin: units.gu(1)
623 y: panel.panelHeight
624 width: parent.width
625 height: parent.height - panel.panelHeight
627 states: [
628 State {
629 name: "narrow"
630 when: overlay.width <= units.gu(60)
631 AnchorChanges { target: notifications; anchors.left: parent.left }
632 },
633 State {
634 name: "wide"
635 when: overlay.width > units.gu(60)
636 AnchorChanges { target: notifications; anchors.left: undefined }
637 PropertyChanges { target: notifications; width: units.gu(38) }
638 }
639 ]
640 }
641 }
643 Binding {
644 target: i18n
645 property: "domain"
646 value: "unity8"
647 }
649 Dialogs {
650 id: dialogs
651 anchors.fill: parent
652 z: overlay.z + 10
653 onPowerOffClicked: {
654 shutdownFadeOutRectangle.enabled = true;
655 shutdownFadeOutRectangle.visible = true;
656 shutdownFadeOut.start();
657 }
658 }
660 Label {
661 id: alphaDisclaimerLabel
662 anchors.centerIn: parent
663 visible: ApplicationManager.fake ? ApplicationManager.fake : false
664 z: dialogs.z + 10
665 text: "EARLY ALPHA\nNOT READY FOR USE"
666 color: "lightgrey"
667 opacity: 0.2
668 font.weight: Font.Black
669 horizontalAlignment: Text.AlignHCenter
670 verticalAlignment: Text.AlignVCenter
671 fontSizeMode: Text.Fit
672 rotation: -45
673 scale: Math.min(parent.width, parent.height) / width
674 }
676 EdgeDemo {
677 id: edgeDemo
678 z: alphaDisclaimerLabel.z + 10
679 greeter: greeter
680 launcher: launcher
681 dash: dash
682 indicators: panel.indicators
683 underlay: underlay
684 }
686 Connections {
687 target: SessionBroadcast
688 onShowHome: showHome()
689 }
691 Rectangle {
692 id: shutdownFadeOutRectangle
693 z: edgeDemo.z + 10
694 enabled: false
695 visible: false
696 color: "black"
697 anchors.fill: parent
698 opacity: 0.0
699 NumberAnimation on opacity {
700 id: shutdownFadeOut
701 from: 0.0
702 to: 1.0
703 onStopped: {
704 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
705 DBusUnitySessionService.Shutdown();
706 }
707 }
708 }
709 }