on 713e430 help sign in
unity8/qml/Shell.qml
717 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 Image {
124 anchors.fill: dash
125 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
126 fillMode: Image.PreserveAspectCrop
127 horizontalAlignment: Image.AlignRight
128 verticalAlignment: Image.AlignTop
129 }
131 Dash {
132 id: dash
133 objectName: "dash"
135 available: !LightDM.Greeter.active
136 hides: [stages, launcher, panel.indicators]
137 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
138 !(panel.indicators.fullyOpened && !sideStageEnabled)
139 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
141 anchors {
142 fill: parent
143 topMargin: panel.panelHeight
144 }
146 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
147 opacity: 1.0 - disappearingAnimationProgress
148 property real disappearingAnimationProgress: stages.showProgress
150 // FIXME: only necessary because stages.showProgress is not animated
151 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
152 }
153 }
155 EdgeDragArea {
156 id: stagesDragArea
157 direction: Direction.Leftwards
159 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
160 width: shell.edgeSize
162 property real progress: stages.width
164 onTouchXChanged: {
165 if (status == DirectionalDragArea.Recognized) {
166 if (ApplicationManager.empty) {
167 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
168 } else {
169 progress = stages.width - stagesDragArea.width + touchX;
170 }
171 }
172 }
174 onDraggingChanged: {
175 if (!dragging) {
176 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
177 stages.show();
178 }
179 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
180 }
181 }
182 }
184 Item {
185 id: stages
186 objectName: "stages"
187 width: parent.width
188 height: parent.height
190 visible: !fullyHidden && !ApplicationManager.empty
192 x: {
193 if (shown) {
194 if (locked || greeter.fakeActiveForApp !== "") {
195 return 0;
196 }
197 return launcher.progress;
198 } else {
199 return stagesDragArea.progress
200 }
201 }
202 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
204 property bool shown: false
205 onShownChanged: {
206 if (shown) {
207 if (ApplicationManager.count > 0) {
208 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
209 }
210 } else {
211 if (ApplicationManager.focusedApplicationId) {
212 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
213 ApplicationManager.unfocusCurrentApplication();
214 }
215 }
216 }
218 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
219 // zero. That breaks the property binding and the function won't be reevaluated once
220 // shell.width is set, with the NaN result staying there for good.
221 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
223 property bool fullyShown: x == 0
224 property bool fullyHidden: x == width
226 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
228 // It might technically not be fullyShown but visually it just looks so.
229 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
231 function show() {
232 shown = true;
233 }
235 function hide() {
236 shown = false;
237 }
239 Connections {
240 target: ApplicationManager
241 onFocusRequested: {
242 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
243 lockscreen.show();
244 }
245 greeter.hide();
246 stages.show();
247 }
249 onFocusedApplicationIdChanged: {
250 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
251 lockscreen.show();
252 }
253 panel.indicators.hide();
254 }
256 onApplicationAdded: {
257 if (greeter.shown) {
258 greeter.hide();
259 }
260 if (!stages.shown) {
261 stages.show();
262 }
263 }
265 onEmptyChanged: {
266 if (ApplicationManager.empty) {
267 stages.hide();
268 }
269 }
270 }
272 Loader {
273 id: applicationsDisplayLoader
274 anchors.fill: parent
276 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
278 Binding {
279 target: applicationsDisplayLoader.item
280 property: "objectName"
281 value: "stage"
282 }
283 Binding {
284 target: applicationsDisplayLoader.item
285 property: "dragAreaWidth"
286 value: shell.edgeSize
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "maximizedAppTopMargin"
291 // Not just using panel.panelHeight as that changes depending on the focused app.
292 value: panel.indicators.panelHeight
293 }
294 Binding {
295 target: applicationsDisplayLoader.item
296 property: "interactive"
297 value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
298 && panel.indicators.fullyClosed
299 }
300 Binding {
301 target: applicationsDisplayLoader.item
302 property: "spreadEnabled"
303 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
304 }
305 }
306 }
308 InputMethod {
309 id: inputMethod
310 objectName: "inputMethod"
311 anchors { fill: parent; topMargin: panel.panelHeight }
312 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
313 }
315 Connections {
316 target: SurfaceManager
317 onSurfaceCreated: {
318 if (surface.type == MirSurfaceItem.InputMethod) {
319 inputMethod.surface = surface;
320 }
321 }
323 onSurfaceDestroyed: {
324 if (inputMethod.surface == surface) {
325 inputMethod.surface = null;
326 surface.parent = null;
327 }
328 if (!surface.parent) {
329 // there's no one displaying it. delete it right away
330 surface.release();
331 }
332 }
333 }
335 Lockscreen {
336 id: lockscreen
337 objectName: "lockscreen"
339 readonly property int backgroundTopMargin: -panel.panelHeight
341 hides: [launcher, panel.indicators]
342 shown: false
343 enabled: true
344 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
345 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
346 y: panel.panelHeight
347 visible: required
348 width: parent.width
349 height: parent.height - panel.panelHeight
350 background: shell.background
351 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
352 minPinLength: 4
353 maxPinLength: 4
355 onEntered: LightDM.Greeter.respond(passphrase);
356 onCancel: greeter.show()
357 onEmergencyCall: {
358 greeter.fakeActiveForApp = "dialer-app"
359 shell.activateApplication("dialer-app")
360 lockscreen.hide()
361 }
363 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
365 Component.onCompleted: {
366 if (greeter.narrowMode) {
367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
368 }
369 }
370 }
372 Connections {
373 target: LightDM.Greeter
375 onShowGreeter: greeter.show()
377 onShowPrompt: {
378 if (greeter.narrowMode) {
379 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
380 lockscreen.show();
381 }
382 }
384 onPromptlessChanged: {
385 if (LightDM.Greeter.promptless) {
386 lockscreen.hide()
387 } else {
388 lockscreen.reset();
389 lockscreen.show();
390 }
391 }
393 onAuthenticationComplete: {
394 if (LightDM.Greeter.promptless) {
395 return;
396 }
397 if (LightDM.Greeter.authenticated) {
398 lockscreen.hide();
399 greeter.login();
400 } else {
401 lockscreen.clear(true);
402 if (greeter.narrowMode) {
403 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
404 }
405 }
406 }
407 }
409 Binding {
410 target: LightDM.Greeter
411 property: "active"
412 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
413 }
415 Rectangle {
416 anchors.fill: parent
417 color: "black"
418 opacity: greeterWrapper.showProgress * 0.8
419 }
421 Item {
422 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
423 id: greeterWrapper
424 x: launcher.progress
425 y: panel.panelHeight
426 width: parent.width
427 height: parent.height - panel.panelHeight
429 Behavior on x {
430 enabled: !launcher.dashSwipe
431 StandardAnimation {}
432 }
434 property bool fullyShown: showProgress === 1.0
435 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
436 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
438 Greeter {
439 id: greeter
440 objectName: "greeter"
442 signal sessionStarted() // helpful for tests
444 property string fakeActiveForApp: ""
446 available: true
447 hides: [launcher, panel.indicators]
448 shown: true
450 defaultBackground: shell.background
452 width: parent.width
453 height: parent.height
455 dragHandleWidth: shell.edgeSize
457 function login() {
458 enabled = false;
459 if (LightDM.Greeter.startSessionSync()) {
460 sessionStarted();
461 greeter.hide();
462 lockscreen.hide();
463 launcher.hide();
464 }
465 enabled = true;
466 }
468 onShownChanged: {
469 if (shown) {
470 if (greeter.narrowMode) {
471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
472 }
473 if (!LightDM.Greeter.promptless) {
474 lockscreen.reset();
475 lockscreen.show();
476 }
477 greeter.fakeActiveForApp = "";
478 greeter.forceActiveFocus();
479 }
480 }
482 onUnlocked: greeter.hide()
483 onSelected: {
484 // Update launcher items for new user
485 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
486 AccountsService.user = user;
487 LauncherModel.setUser(user);
488 }
490 onTease: launcher.tease()
492 Binding {
493 target: ApplicationManager
494 property: "suspended"
495 value: greeter.shown && greeterWrapper.showProgress == 1
496 }
497 }
498 }
500 Connections {
501 id: powerConnection
502 target: Powerd
504 onDisplayPowerStateChange: {
505 // We ignore any display-off signals when the proximity sensor
506 // is active. This usually indicates something like a phone call.
507 if (status == Powerd.Off && reason != Powerd.Proximity) {
508 greeter.showNow();
509 }
511 // No reason to chew demo CPU when user isn't watching
512 if (status == Powerd.Off) {
513 edgeDemo.paused = true;
514 } else if (status == Powerd.On) {
515 edgeDemo.paused = false;
516 }
517 }
518 }
520 function showHome() {
521 if (edgeDemo.running) {
522 return
523 }
525 if (LightDM.Greeter.active) {
526 if (!LightDM.Greeter.promptless) {
527 lockscreen.show()
528 }
529 greeter.hide()
530 }
532 var animate = !LightDM.Greeter.active && !stages.shown
533 dash.setCurrentScope("clickscope", animate, false)
534 stages.hide()
535 }
537 function showDash() {
538 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
539 return;
540 }
542 if (!stages.locked) {
543 stages.hide();
544 launcher.fadeOut();
545 } else {
546 launcher.switchToNextState("visible");
547 }
549 if (greeter.shown) {
550 greeter.hideRight();
551 launcher.fadeOut();
552 }
553 }
555 Item {
556 id: overlay
557 z: 10
559 anchors.fill: parent
561 Panel {
562 id: panel
563 objectName: "panel"
564 anchors.fill: parent //because this draws indicator menus
565 indicators {
566 hides: [launcher]
567 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
568 contentEnabled: edgeDemo.panelContentEnabled
569 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
570 panelHeight: units.gu(3)
571 }
573 property bool topmostApplicationIsFullscreen:
574 ApplicationManager.focusedApplicationId &&
575 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
577 fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
578 && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
579 }
581 Launcher {
582 id: launcher
583 objectName: "launcher"
585 readonly property bool dashSwipe: progress > 0
587 anchors.top: parent.top
588 anchors.bottom: parent.bottom
589 width: parent.width
590 dragAreaWidth: shell.edgeSize
591 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
593 onShowDashHome: showHome()
594 onDash: showDash()
595 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
596 onLauncherApplicationSelected: {
597 if (greeter.fakeActiveForApp !== "") {
598 lockscreen.show()
599 }
600 if (!edgeDemo.running)
601 shell.activateApplication(appId)
602 }
603 onShownChanged: {
604 if (shown) {
605 panel.indicators.hide()
606 }
607 }
608 }
610 Rectangle {
611 id: modalNotificationBackground
613 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
614 color: "#000000"
615 anchors.fill: parent
616 opacity: 0.5
618 MouseArea {
619 anchors.fill: parent
620 }
621 }
623 Notifications {
624 id: notifications
626 model: NotificationBackend.Model
627 margin: units.gu(1)
629 y: panel.panelHeight
630 width: parent.width
631 height: parent.height - panel.panelHeight
633 states: [
634 State {
635 name: "narrow"
636 when: overlay.width <= units.gu(60)
637 AnchorChanges { target: notifications; anchors.left: parent.left }
638 },
639 State {
640 name: "wide"
641 when: overlay.width > units.gu(60)
642 AnchorChanges { target: notifications; anchors.left: undefined }
643 PropertyChanges { target: notifications; width: units.gu(38) }
644 }
645 ]
646 }
647 }
649 Binding {
650 target: i18n
651 property: "domain"
652 value: "unity8"
653 }
655 Dialogs {
656 id: dialogs
657 anchors.fill: parent
658 z: overlay.z + 10
659 onPowerOffClicked: {
660 shutdownFadeOutRectangle.enabled = true;
661 shutdownFadeOutRectangle.visible = true;
662 shutdownFadeOut.start();
663 }
664 }
666 Label {
667 id: alphaDisclaimerLabel
668 anchors.centerIn: parent
669 visible: ApplicationManager.fake ? ApplicationManager.fake : false
670 z: dialogs.z + 10
671 text: "EARLY ALPHA\nNOT READY FOR USE"
672 color: "lightgrey"
673 opacity: 0.2
674 font.weight: Font.Black
675 horizontalAlignment: Text.AlignHCenter
676 verticalAlignment: Text.AlignVCenter
677 fontSizeMode: Text.Fit
678 rotation: -45
679 scale: Math.min(parent.width, parent.height) / width
680 }
682 EdgeDemo {
683 id: edgeDemo
684 z: alphaDisclaimerLabel.z + 10
685 greeter: greeter
686 launcher: launcher
687 dash: dash
688 indicators: panel.indicators
689 underlay: underlay
690 }
692 Connections {
693 target: SessionBroadcast
694 onShowHome: showHome()
695 }
697 Rectangle {
698 id: shutdownFadeOutRectangle
699 z: edgeDemo.z + 10
700 enabled: false
701 visible: false
702 color: "black"
703 anchors.fill: parent
704 opacity: 0.0
705 NumberAnimation on opacity {
706 id: shutdownFadeOut
707 from: 0.0
708 to: 1.0
709 onStopped: {
710 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
711 DBusUnitySessionService.Shutdown();
712 }
713 }
714 }
715 }