on 994879f help sign in
unity8/qml/Shell.qml
752 lines 26 kB history raw
1/*
2 * Copyright (C) 2013-2015 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 QtQuick.Window 2.0
19import AccountsService 0.1
20import GSettings 1.0
21import Unity.Application 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Connectivity 0.1
27import Unity.Launcher 0.1
28import Utils 0.1
29import LightDM 0.1 as LightDM
30import Powerd 0.1
31import SessionBroadcast 0.1
32import "Greeter"
33import "Launcher"
34import "Panel"
35import "Components"
36import "Notifications"
37import "Stages"
38import "Tutorial"
39import "Wizard"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
43import Unity.Indicators 0.1 as Indicators
45Item {
46 id: shell
48 // to be set from outside
49 property int orientationAngle: 0
50 property int orientation
51 property QtObject deviceSpecificOrientationOverrides
52 property int primaryOrientation
53 property int nativeOrientation
54 property real nativeWidth
55 property real nativeHeight
56 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
57 property bool beingResized
58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59 property string mode: "full-greeter"
60 function updateFocusedAppOrientation() {
61 applicationsDisplayLoader.item.updateFocusedAppOrientation();
62 }
63 function updateFocusedAppOrientationAnimated() {
64 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
65 }
67 // to be read from outside
68 readonly property int mainAppWindowOrientationAngle:
69 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
71 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
72 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
73 && (!greeter || !greeter.animating)
75 readonly property bool showingGreeter: greeter && greeter.shown
77 property bool startingUp: true
78 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
80 property int supportedOrientations: {
81 if (startingUp) {
82 // Ensure we don't rotate during start up
83 return Qt.PrimaryOrientation;
84 } else if (greeter && greeter.shown) {
85 return Qt.PrimaryOrientation;
86 } else if (mainApp) {
87 return mainApp.supportedOrientations;
88 } else {
89 // we just don't care
90 return Qt.PortraitOrientation
91 | Qt.LandscapeOrientation
92 | Qt.InvertedPortraitOrientation
93 | Qt.InvertedLandscapeOrientation;
94 }
95 }
97 // For autopilot consumption
98 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
100 // internal props from here onwards
101 readonly property var mainApp:
102 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
104 // Disable everything while greeter is waiting, so that the user can't swipe
105 // the greeter or launcher until we know whether the session is locked.
106 enabled: greeter && !greeter.waiting
108 property real edgeSize: units.gu(2)
109 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
110 property url background: asImageTester.status == Image.Ready ? asImageTester.source
111 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
113 readonly property alias greeter: greeterLoader.item
115 function activateApplication(appId) {
116 if (ApplicationManager.findApplication(appId)) {
117 ApplicationManager.requestFocusApplication(appId);
118 } else {
119 var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
120 : ApplicationManager.NoFlag;
121 ApplicationManager.startApplication(appId, execFlags);
122 }
123 }
125 function startLockedApp(app) {
126 if (greeter.locked) {
127 greeter.lockedApp = app;
128 }
129 shell.activateApplication(app);
130 }
132 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
133 Image {
134 id: asImageTester
135 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
136 height: 0
137 width: 0
138 sourceSize.height: 0
139 sourceSize.width: 0
140 }
142 GSettings {
143 id: backgroundSettings
144 schema.id: "org.gnome.desktop.background"
145 }
147 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
148 Image {
149 id: gsImageTester
150 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
151 height: 0
152 width: 0
153 sourceSize.height: 0
154 sourceSize.width: 0
155 }
157 Binding {
158 target: LauncherModel
159 property: "applicationManager"
160 value: ApplicationManager
161 }
163function serialize(object, maxDepth) {
164 function _processObject(object, maxDepth, level) {
165 var output = Array()
166 var pad = " "
167 if (maxDepth == undefined) {
168 maxDepth = -1
169 }
170 if (level == undefined) {
171 level = 0
172 }
173 var padding = Array(level + 1).join(pad)
175 output.push((Array.isArray(object) ? "[" : "{"))
176 var fields = Array()
177 for (var key in object) {
178 var keyText = Array.isArray(object) ? "" : ("\"" + key + "\": ")
179 if (typeof (object[key]) == "object" && key != "parent" && maxDepth != 0) {
180 var res = _processObject(object[key], maxDepth > 0 ? maxDepth - 1 : -1, level + 1)
181 fields.push(padding + pad + keyText + res)
182 } else {
183 fields.push(padding + pad + keyText + "\"" + object[key] + "\"")
184 }
185 }
186 output.push(fields.join(",\n"))
187 output.push(padding + (Array.isArray(object) ? "]" : "}"))
189 return output.join("\n")
190 }
192 return _processObject(object, maxDepth)
195 Component.onCompleted: {
196 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
197 if (ApplicationManager.count > 0) {
198 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
199 }
200 finishStartUpTimer.start();
201 }
203 VolumeControl {
204 id: volumeControl
205 }
207 DashCommunicator {
208 id: dash
209 objectName: "dashCommunicator"
210 }
212 PhysicalKeysMapper {
213 id: physicalKeysMapper
214 objectName: "physicalKeysMapper"
216 onPowerKeyLongPressed: dialogs.showPowerDialog()
217 onVolumeDownTriggered: volumeControl.volumeDown();
218 onVolumeUpTriggered: volumeControl.volumeUp();
219 onScreenshotTriggered: screenGrabber.capture();
220 }
222 ScreenGrabber {
223 id: screenGrabber
224 z: dialogs.z + 10
225 }
227 WindowKeysFilter {
228 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
229 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
230 }
232 HomeKeyWatcher {
233 onActivated: { launcher.fadeOut(); shell.showHome(); }
234 }
236 Item {
237 id: stages
238 objectName: "stages"
239 width: parent.width
240 height: parent.height
242 Connections {
243 target: ApplicationManager
245 // This signal is also fired when we try to focus the current app
246 // again. We rely on this!
247 onFocusedApplicationIdChanged: {
248 var appId = ApplicationManager.focusedApplicationId;
250 if (tutorial.running && appId != "" && appId != "unity8-dash") {
251 // If this happens on first boot, we may be in edge
252 // tutorial or wizard while receiving a call. But a call
253 // is more important than wizard so just bail out of those.
254 tutorial.finish();
255 wizard.hide();
256 }
258 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
259 // If we are in the middle of a call, make dialer lockedApp and show it.
260 // This can happen if user backs out of dialer back to greeter, then
261 // launches dialer again.
262 greeter.lockedApp = appId;
263 }
264 greeter.notifyAppFocused(appId);
266 panel.indicators.hide();
267 }
269 onApplicationAdded: {
270 launcher.hide();
271 }
272 }
274 Loader {
275 id: applicationsDisplayLoader
276 objectName: "applicationsDisplayLoader"
277 anchors.fill: parent
279 // When we have a locked app, we only want to show that one app.
280 // FIXME: do this in a less traumatic way. We currently only allow
281 // locked apps in phone mode (see FIXME in Lockscreen component in
282 // this same file). When that changes, we need to do something
283 // nicer here. But this code is currently just to prevent a
284 // theoretical attack where user enters lockedApp mode, then makes
285 // the screen larger (maybe connects to monitor) and tries to enter
286 // tablet mode.
288 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
289 ? "phone"
290 : shell.usageScenario
291 source: {
292 if(shell.mode === "greeter") {
293 return "Stages/ShimStage.qml"
294 } else if (applicationsDisplayLoader.usageScenario === "phone") {
295 return "Stages/PhoneStage.qml";
296 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
297 return "Stages/TabletStage.qml";
298 } else {
299 return "Stages/DesktopStage.qml";
300 }
301 }
303 property bool interactive: tutorial.spreadEnabled
304 && (!greeter || !greeter.shown)
305 && panel.indicators.fullyClosed
306 && launcher.progress == 0
307 && !notifications.useModal
309 onInteractiveChanged: { if (interactive) { focus = true; } }
311 Binding {
312 target: applicationsDisplayLoader.item
313 property: "objectName"
314 value: "stage"
315 }
316 Binding {
317 target: applicationsDisplayLoader.item
318 property: "dragAreaWidth"
319 value: shell.edgeSize
320 }
321 Binding {
322 target: applicationsDisplayLoader.item
323 property: "maximizedAppTopMargin"
324 // Not just using panel.panelHeight as that changes depending on the focused app.
325 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
326 }
327 Binding {
328 target: applicationsDisplayLoader.item
329 property: "interactive"
330 value: applicationsDisplayLoader.interactive
331 }
332 Binding {
333 target: applicationsDisplayLoader.item
334 property: "spreadEnabled"
335 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
336 }
337 Binding {
338 target: applicationsDisplayLoader.item
339 property: "inverseProgress"
340 value: greeter && greeter.locked ? 0 : launcher.progress
341 }
342 Binding {
343 target: applicationsDisplayLoader.item
344 property: "shellOrientationAngle"
345 value: shell.orientationAngle
346 }
347 Binding {
348 target: applicationsDisplayLoader.item
349 property: "shellOrientation"
350 value: shell.orientation
351 }
352 Binding {
353 target: applicationsDisplayLoader.item
354 property: "background"
355 value: shell.background
356 }
357 Binding {
358 target: applicationsDisplayLoader.item
359 property: "shellPrimaryOrientation"
360 value: shell.primaryOrientation
361 }
362 Binding {
363 target: applicationsDisplayLoader.item
364 property: "nativeOrientation"
365 value: shell.nativeOrientation
366 }
367 Binding {
368 target: applicationsDisplayLoader.item
369 property: "nativeWidth"
370 value: shell.nativeWidth
371 }
372 Binding {
373 target: applicationsDisplayLoader.item
374 property: "deviceSpecificOrientationOverrides"
375 value: shell.deviceSpecificOrientationOverrides
376 }
377 Binding {
378 target: applicationsDisplayLoader.item
379 property: "nativeHeight"
380 value: shell.nativeHeight
381 }
382 Binding {
383 target: applicationsDisplayLoader.item
384 property: "beingResized"
385 value: shell.beingResized
386 }
387 Binding {
388 target: applicationsDisplayLoader.item
389 property: "keepDashRunning"
390 value: launcher.shown || launcher.dashSwipe
391 }
392 Binding {
393 target: applicationsDisplayLoader.item
394 property: "suspended"
395 value: greeter.shown
396 }
397 Binding {
398 target: applicationsDisplayLoader.item
399 property: "altTabPressed"
400 value: physicalKeysMapper.altTabPressed
401 }
402 }
404 Tutorial {
405 id: tutorial
406 objectName: "tutorial"
407 anchors.fill: parent
408 active: AccountsService.demoEdges
409 paused: LightDM.Greeter.active
410 launcher: launcher
411 panel: panel
412 edgeSize: shell.edgeSize
414 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
415 // we'll tell the tutorial to avoid using them on the Desktop. The
416 // Desktop doesn't use the same spread design anyway. The tutorial is
417 // all a bit of a placeholder on non-phone form factors right now.
418 // When the design team gives us more guidance, we can do something
419 // more clever here.
420 // TODO: use DeviceConfiguration instead of checking source
421 useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
423 onFinished: {
424 AccountsService.demoEdges = false;
425 active = false; // for immediate response / if AS is having problems
426 }
427 }
428 }
430 InputMethod {
431 id: inputMethod
432 objectName: "inputMethod"
433 anchors { fill: parent; topMargin: panel.panelHeight }
434 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
435 }
437 Connections {
438 target: SurfaceManager
439 onSurfaceCreated: {
440 if (surface.type == MirSurfaceItem.InputMethod) {
441 inputMethod.surface = surface;
442 }
443 }
445 onSurfaceDestroyed: {
446 if (inputMethod.surface == surface) {
447 inputMethod.surface = null;
448 surface.parent = null;
449 }
450 if (!surface.parent) {
451 // there's no one displaying it. delete it right away
452 surface.release();
453 }
454 }
455 }
456 Connections {
457 target: SessionManager
458 onSessionStopping: {
459 if (!session.parentSession && !session.application) {
460 // nothing is using it. delete it right away
461 session.release();
462 }
463 }
464 }
466 Loader {
467 id: greeterLoader
468 anchors.fill: parent
469 anchors.topMargin: panel.panelHeight
470 sourceComponent: shell.mode != "shell" ? integratedGreeter :
471 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
472 onLoaded: {
473 item.objectName = "greeter"
474 }
475 }
477 Component {
478 id: integratedGreeter
479 Greeter {
481 hides: [launcher, panel.indicators]
482 tabletMode: shell.usageScenario != "phone"
483 launcherOffset: launcher.progress
484 forcedUnlock: tutorial.running
485 background: shell.background
487 // avoid overlapping with Launcher's edge drag area
488 // FIXME: Fix TouchRegistry & friends and remove this workaround
489 // Issue involves launcher's DDA getting disabled on a long
490 // left-edge drag
491 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
493 onSessionStarted: {
494 launcher.hide();
495 }
497 onTease: {
498 if (!tutorial.running) {
499 launcher.tease();
500 }
501 }
503 onEmergencyCall: startLockedApp("dialer-app")
504 }
505 }
507 Timer {
508 // See powerConnection for why this is useful
509 id: showGreeterDelayed
510 interval: 1
511 onTriggered: {
512 greeter.forceShow();
513 }
514 }
516 Connections {
517 id: callConnection
518 target: callManager
520 onHasCallsChanged: {
521 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
522 // We just received an incoming call while locked. The
523 // indicator will have already launched dialer-app for us, but
524 // there is a race between "hasCalls" changing and the dialer
525 // starting up. So in case we lose that race, we'll start/
526 // focus the dialer ourselves here too. Even if the indicator
527 // didn't launch the dialer for some reason (or maybe a call
528 // started via some other means), if an active call is
529 // happening, we want to be in the dialer.
530 startLockedApp("dialer-app")
531 }
532 }
533 }
535 Connections {
536 id: powerConnection
537 target: Powerd
539 onStatusChanged: {
540 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
541 !callManager.hasCalls && !tutorial.running) {
542 // We don't want to simply call greeter.showNow() here, because
543 // that will take too long. Qt will delay button event
544 // handling until the greeter is done loading and may think the
545 // user held down the power button the whole time, leading to a
546 // power dialog being shown. Instead, delay showing the
547 // greeter until we've finished handling the event. We could
548 // make the greeter load asynchronously instead, but that
549 // introduces a whole host of timing issues, especially with
550 // its animations. So this is simpler.
551 showGreeterDelayed.start();
552 }
553 }
554 }
556 function showHome() {
557 if (tutorial.running) {
558 return
559 }
561 greeter.notifyAboutToFocusApp("unity8-dash");
563 var animate = !LightDM.Greeter.active && !stages.shown
564 dash.setCurrentScope(0, animate, false)
565 ApplicationManager.requestFocusApplication("unity8-dash")
566 }
568 function showDash() {
569 if (greeter.notifyShowingDashFromDrag()) {
570 launcher.fadeOut();
571 }
573 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
574 ApplicationManager.requestFocusApplication("unity8-dash")
575 launcher.fadeOut();
576 }
577 }
579 Item {
580 id: overlay
581 z: 10
583 anchors.fill: parent
585 Panel {
586 id: panel
587 objectName: "panel"
588 anchors.fill: parent //because this draws indicator menus
589 indicators {
590 hides: [launcher]
591 available: tutorial.panelEnabled
592 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
593 && (!greeter || !greeter.hasLockedApp)
594 contentEnabled: tutorial.panelContentEnabled
595 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
597 minimizedPanelHeight: units.gu(3)
598 expandedPanelHeight: units.gu(7)
600 indicatorsModel: Indicators.IndicatorsModel {
601 // tablet and phone both use the same profile
602 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
603 Component.onCompleted: load();
604 }
605 }
607 callHint {
608 greeterShown: greeter.shown
609 }
611 property bool mainAppIsFullscreen: shell.mainApp && shell.mainApp.fullscreen
612 fullscreenMode: (mainAppIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
613 || greeter.hasLockedApp
614 }
616 Launcher {
617 id: launcher
618 objectName: "launcher"
620 readonly property bool dashSwipe: progress > 0
622 anchors.top: parent.top
623 anchors.topMargin: inverted ? 0 : panel.panelHeight
624 anchors.bottom: parent.bottom
625 width: parent.width
626 dragAreaWidth: shell.edgeSize
627 available: tutorial.launcherEnabled
628 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
629 && !greeter.hasLockedApp
630 inverted: shell.usageScenario !== "desktop"
631 shadeBackground: !tutorial.running
633 onShowDashHome: showHome()
634 onDash: showDash()
635 onDashSwipeChanged: {
636 if (dashSwipe) {
637 dash.setCurrentScope(0, false, true)
638 }
639 }
640 onLauncherApplicationSelected: {
641 if (!tutorial.running) {
642 greeter.notifyAboutToFocusApp(appId);
643 shell.activateApplication(appId)
644 }
645 }
646 onShownChanged: {
647 if (shown) {
648 panel.indicators.hide()
649 }
650 }
651 }
653 Wizard {
654 id: wizard
655 objectName: "wizard"
656 anchors.fill: parent
657 background: shell.background
659 function unlockWhenDoneWithWizard() {
660 if (!active) {
661 Connectivity.unlockAllModems();
662 }
663 }
665 Component.onCompleted: unlockWhenDoneWithWizard()
666 onActiveChanged: unlockWhenDoneWithWizard()
667 }
669 Rectangle {
670 id: modalNotificationBackground
672 visible: notifications.useModal
673 color: "#000000"
674 anchors.fill: parent
675 opacity: 0.9
677 MouseArea {
678 anchors.fill: parent
679 }
680 }
682 Notifications {
683 id: notifications
685 model: NotificationBackend.Model
686 margin: units.gu(1)
688 y: topmostIsFullscreen ? 0 : panel.panelHeight
689 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
691 states: [
692 State {
693 name: "narrow"
694 when: overlay.width <= units.gu(60)
695 AnchorChanges {
696 target: notifications
697 anchors.left: parent.left
698 anchors.right: parent.right
699 }
700 },
701 State {
702 name: "wide"
703 when: overlay.width > units.gu(60)
704 AnchorChanges {
705 target: notifications
706 anchors.left: undefined
707 anchors.right: parent.right
708 }
709 PropertyChanges { target: notifications; width: units.gu(38) }
710 }
711 ]
712 }
713 }
715 Dialogs {
716 id: dialogs
717 objectName: "dialogs"
718 anchors.fill: parent
719 z: overlay.z + 10
720 onPowerOffClicked: {
721 shutdownFadeOutRectangle.enabled = true;
722 shutdownFadeOutRectangle.visible = true;
723 shutdownFadeOut.start();
724 }
725 }
727 Connections {
728 target: SessionBroadcast
729 onShowHome: showHome()
730 }
732 Rectangle {
733 id: shutdownFadeOutRectangle
734 z: screenGrabber.z + 10
735 enabled: false
736 visible: false
737 color: "black"
738 anchors.fill: parent
739 opacity: 0.0
740 NumberAnimation on opacity {
741 id: shutdownFadeOut
742 from: 0.0
743 to: 1.0
744 onStopped: {
745 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
746 DBusUnitySessionService.Shutdown();
747 }
748 }
749 }
750 }