on bd74b71 help sign in
unity8/qml/Shell.qml
755 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 console.log("JOSH: Shell: " + deviceSpecificOrientationOverrides.hello);
197 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
198 if (ApplicationManager.count > 0) {
199 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
200 }
201 finishStartUpTimer.start();
202 }
204 VolumeControl {
205 id: volumeControl
206 }
208 DashCommunicator {
209 id: dash
210 objectName: "dashCommunicator"
211 }
213 PhysicalKeysMapper {
214 id: physicalKeysMapper
215 objectName: "physicalKeysMapper"
217 onPowerKeyLongPressed: dialogs.showPowerDialog()
218 onVolumeDownTriggered: volumeControl.volumeDown();
219 onVolumeUpTriggered: volumeControl.volumeUp();
220 onScreenshotTriggered: screenGrabber.capture();
221 }
223 ScreenGrabber {
224 id: screenGrabber
225 z: dialogs.z + 10
226 }
228 WindowKeysFilter {
229 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
230 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
231 }
233 HomeKeyWatcher {
234 onActivated: { launcher.fadeOut(); shell.showHome(); }
235 }
237 Item {
238 id: stages
239 objectName: "stages"
240 width: parent.width
241 height: parent.height
243 Connections {
244 target: ApplicationManager
246 // This signal is also fired when we try to focus the current app
247 // again. We rely on this!
248 onFocusedApplicationIdChanged: {
249 var appId = ApplicationManager.focusedApplicationId;
251 if (tutorial.running && appId != "" && appId != "unity8-dash") {
252 // If this happens on first boot, we may be in edge
253 // tutorial or wizard while receiving a call. But a call
254 // is more important than wizard so just bail out of those.
255 tutorial.finish();
256 wizard.hide();
257 }
259 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
260 // If we are in the middle of a call, make dialer lockedApp and show it.
261 // This can happen if user backs out of dialer back to greeter, then
262 // launches dialer again.
263 greeter.lockedApp = appId;
264 }
265 greeter.notifyAppFocused(appId);
267 panel.indicators.hide();
268 }
270 onApplicationAdded: {
271 launcher.hide();
272 }
273 }
275 Loader {
276 id: applicationsDisplayLoader
277 objectName: "applicationsDisplayLoader"
278 anchors.fill: parent
280 // When we have a locked app, we only want to show that one app.
281 // FIXME: do this in a less traumatic way. We currently only allow
282 // locked apps in phone mode (see FIXME in Lockscreen component in
283 // this same file). When that changes, we need to do something
284 // nicer here. But this code is currently just to prevent a
285 // theoretical attack where user enters lockedApp mode, then makes
286 // the screen larger (maybe connects to monitor) and tries to enter
287 // tablet mode.
289 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
290 ? "phone"
291 : shell.usageScenario
292 source: {
293 if(shell.mode === "greeter") {
294 return "Stages/ShimStage.qml"
295 } else if (applicationsDisplayLoader.usageScenario === "phone") {
296 return "Stages/PhoneStage.qml";
297 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
298 return "Stages/TabletStage.qml";
299 } else {
300 return "Stages/DesktopStage.qml";
301 }
302 }
304 onLoaded: console.log("JOSH in shell loader: " + deviceSpecificOrientationOverrides.hello)
306 property bool interactive: tutorial.spreadEnabled
307 && (!greeter || !greeter.shown)
308 && panel.indicators.fullyClosed
309 && launcher.progress == 0
310 && !notifications.useModal
312 onInteractiveChanged: { if (interactive) { focus = true; } }
314 Binding {
315 target: applicationsDisplayLoader.item
316 property: "objectName"
317 value: "stage"
318 }
319 Binding {
320 target: applicationsDisplayLoader.item
321 property: "dragAreaWidth"
322 value: shell.edgeSize
323 }
324 Binding {
325 target: applicationsDisplayLoader.item
326 property: "maximizedAppTopMargin"
327 // Not just using panel.panelHeight as that changes depending on the focused app.
328 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
329 }
330 Binding {
331 target: applicationsDisplayLoader.item
332 property: "interactive"
333 value: applicationsDisplayLoader.interactive
334 }
335 Binding {
336 target: applicationsDisplayLoader.item
337 property: "spreadEnabled"
338 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
339 }
340 Binding {
341 target: applicationsDisplayLoader.item
342 property: "inverseProgress"
343 value: greeter && greeter.locked ? 0 : launcher.progress
344 }
345 Binding {
346 target: applicationsDisplayLoader.item
347 property: "shellOrientationAngle"
348 value: shell.orientationAngle
349 }
350 Binding {
351 target: applicationsDisplayLoader.item
352 property: "shellOrientation"
353 value: shell.orientation
354 }
355 Binding {
356 target: applicationsDisplayLoader.item
357 property: "background"
358 value: shell.background
359 }
360 Binding {
361 target: applicationsDisplayLoader.item
362 property: "shellPrimaryOrientation"
363 value: shell.primaryOrientation
364 }
365 Binding {
366 target: applicationsDisplayLoader.item
367 property: "nativeOrientation"
368 value: shell.nativeOrientation
369 }
370 Binding {
371 target: applicationsDisplayLoader.item
372 property: "nativeWidth"
373 value: shell.nativeWidth
374 }
375 Binding {
376 target: applicationsDisplayLoader.item
377 property: "deviceSpecificOrientationOverrides"
378 value: shell.deviceSpecificOrientationOverrides
379 }
380 Binding {
381 target: applicationsDisplayLoader.item
382 property: "nativeHeight"
383 value: shell.nativeHeight
384 }
385 Binding {
386 target: applicationsDisplayLoader.item
387 property: "beingResized"
388 value: shell.beingResized
389 }
390 Binding {
391 target: applicationsDisplayLoader.item
392 property: "keepDashRunning"
393 value: launcher.shown || launcher.dashSwipe
394 }
395 Binding {
396 target: applicationsDisplayLoader.item
397 property: "suspended"
398 value: greeter.shown
399 }
400 Binding {
401 target: applicationsDisplayLoader.item
402 property: "altTabPressed"
403 value: physicalKeysMapper.altTabPressed
404 }
405 }
407 Tutorial {
408 id: tutorial
409 objectName: "tutorial"
410 anchors.fill: parent
411 active: AccountsService.demoEdges
412 paused: LightDM.Greeter.active
413 launcher: launcher
414 panel: panel
415 edgeSize: shell.edgeSize
417 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
418 // we'll tell the tutorial to avoid using them on the Desktop. The
419 // Desktop doesn't use the same spread design anyway. The tutorial is
420 // all a bit of a placeholder on non-phone form factors right now.
421 // When the design team gives us more guidance, we can do something
422 // more clever here.
423 // TODO: use DeviceConfiguration instead of checking source
424 useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
426 onFinished: {
427 AccountsService.demoEdges = false;
428 active = false; // for immediate response / if AS is having problems
429 }
430 }
431 }
433 InputMethod {
434 id: inputMethod
435 objectName: "inputMethod"
436 anchors { fill: parent; topMargin: panel.panelHeight }
437 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
438 }
440 Connections {
441 target: SurfaceManager
442 onSurfaceCreated: {
443 if (surface.type == MirSurfaceItem.InputMethod) {
444 inputMethod.surface = surface;
445 }
446 }
448 onSurfaceDestroyed: {
449 if (inputMethod.surface == surface) {
450 inputMethod.surface = null;
451 surface.parent = null;
452 }
453 if (!surface.parent) {
454 // there's no one displaying it. delete it right away
455 surface.release();
456 }
457 }
458 }
459 Connections {
460 target: SessionManager
461 onSessionStopping: {
462 if (!session.parentSession && !session.application) {
463 // nothing is using it. delete it right away
464 session.release();
465 }
466 }
467 }
469 Loader {
470 id: greeterLoader
471 anchors.fill: parent
472 anchors.topMargin: panel.panelHeight
473 sourceComponent: shell.mode != "shell" ? integratedGreeter :
474 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
475 onLoaded: {
476 item.objectName = "greeter"
477 }
478 }
480 Component {
481 id: integratedGreeter
482 Greeter {
484 hides: [launcher, panel.indicators]
485 tabletMode: shell.usageScenario != "phone"
486 launcherOffset: launcher.progress
487 forcedUnlock: tutorial.running
488 background: shell.background
490 // avoid overlapping with Launcher's edge drag area
491 // FIXME: Fix TouchRegistry & friends and remove this workaround
492 // Issue involves launcher's DDA getting disabled on a long
493 // left-edge drag
494 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
496 onSessionStarted: {
497 launcher.hide();
498 }
500 onTease: {
501 if (!tutorial.running) {
502 launcher.tease();
503 }
504 }
506 onEmergencyCall: startLockedApp("dialer-app")
507 }
508 }
510 Timer {
511 // See powerConnection for why this is useful
512 id: showGreeterDelayed
513 interval: 1
514 onTriggered: {
515 greeter.forceShow();
516 }
517 }
519 Connections {
520 id: callConnection
521 target: callManager
523 onHasCallsChanged: {
524 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
525 // We just received an incoming call while locked. The
526 // indicator will have already launched dialer-app for us, but
527 // there is a race between "hasCalls" changing and the dialer
528 // starting up. So in case we lose that race, we'll start/
529 // focus the dialer ourselves here too. Even if the indicator
530 // didn't launch the dialer for some reason (or maybe a call
531 // started via some other means), if an active call is
532 // happening, we want to be in the dialer.
533 startLockedApp("dialer-app")
534 }
535 }
536 }
538 Connections {
539 id: powerConnection
540 target: Powerd
542 onStatusChanged: {
543 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
544 !callManager.hasCalls && !tutorial.running) {
545 // We don't want to simply call greeter.showNow() here, because
546 // that will take too long. Qt will delay button event
547 // handling until the greeter is done loading and may think the
548 // user held down the power button the whole time, leading to a
549 // power dialog being shown. Instead, delay showing the
550 // greeter until we've finished handling the event. We could
551 // make the greeter load asynchronously instead, but that
552 // introduces a whole host of timing issues, especially with
553 // its animations. So this is simpler.
554 showGreeterDelayed.start();
555 }
556 }
557 }
559 function showHome() {
560 if (tutorial.running) {
561 return
562 }
564 greeter.notifyAboutToFocusApp("unity8-dash");
566 var animate = !LightDM.Greeter.active && !stages.shown
567 dash.setCurrentScope(0, animate, false)
568 ApplicationManager.requestFocusApplication("unity8-dash")
569 }
571 function showDash() {
572 if (greeter.notifyShowingDashFromDrag()) {
573 launcher.fadeOut();
574 }
576 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
577 ApplicationManager.requestFocusApplication("unity8-dash")
578 launcher.fadeOut();
579 }
580 }
582 Item {
583 id: overlay
584 z: 10
586 anchors.fill: parent
588 Panel {
589 id: panel
590 objectName: "panel"
591 anchors.fill: parent //because this draws indicator menus
592 indicators {
593 hides: [launcher]
594 available: tutorial.panelEnabled
595 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
596 && (!greeter || !greeter.hasLockedApp)
597 contentEnabled: tutorial.panelContentEnabled
598 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
600 minimizedPanelHeight: units.gu(3)
601 expandedPanelHeight: units.gu(7)
603 indicatorsModel: Indicators.IndicatorsModel {
604 // tablet and phone both use the same profile
605 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
606 Component.onCompleted: load();
607 }
608 }
610 callHint {
611 greeterShown: greeter.shown
612 }
614 property bool mainAppIsFullscreen: shell.mainApp && shell.mainApp.fullscreen
615 fullscreenMode: (mainAppIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
616 || greeter.hasLockedApp
617 }
619 Launcher {
620 id: launcher
621 objectName: "launcher"
623 readonly property bool dashSwipe: progress > 0
625 anchors.top: parent.top
626 anchors.topMargin: inverted ? 0 : panel.panelHeight
627 anchors.bottom: parent.bottom
628 width: parent.width
629 dragAreaWidth: shell.edgeSize
630 available: tutorial.launcherEnabled
631 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
632 && !greeter.hasLockedApp
633 inverted: shell.usageScenario !== "desktop"
634 shadeBackground: !tutorial.running
636 onShowDashHome: showHome()
637 onDash: showDash()
638 onDashSwipeChanged: {
639 if (dashSwipe) {
640 dash.setCurrentScope(0, false, true)
641 }
642 }
643 onLauncherApplicationSelected: {
644 if (!tutorial.running) {
645 greeter.notifyAboutToFocusApp(appId);
646 shell.activateApplication(appId)
647 }
648 }
649 onShownChanged: {
650 if (shown) {
651 panel.indicators.hide()
652 }
653 }
654 }
656 Wizard {
657 id: wizard
658 objectName: "wizard"
659 anchors.fill: parent
660 background: shell.background
662 function unlockWhenDoneWithWizard() {
663 if (!active) {
664 Connectivity.unlockAllModems();
665 }
666 }
668 Component.onCompleted: unlockWhenDoneWithWizard()
669 onActiveChanged: unlockWhenDoneWithWizard()
670 }
672 Rectangle {
673 id: modalNotificationBackground
675 visible: notifications.useModal
676 color: "#000000"
677 anchors.fill: parent
678 opacity: 0.9
680 MouseArea {
681 anchors.fill: parent
682 }
683 }
685 Notifications {
686 id: notifications
688 model: NotificationBackend.Model
689 margin: units.gu(1)
691 y: topmostIsFullscreen ? 0 : panel.panelHeight
692 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
694 states: [
695 State {
696 name: "narrow"
697 when: overlay.width <= units.gu(60)
698 AnchorChanges {
699 target: notifications
700 anchors.left: parent.left
701 anchors.right: parent.right
702 }
703 },
704 State {
705 name: "wide"
706 when: overlay.width > units.gu(60)
707 AnchorChanges {
708 target: notifications
709 anchors.left: undefined
710 anchors.right: parent.right
711 }
712 PropertyChanges { target: notifications; width: units.gu(38) }
713 }
714 ]
715 }
716 }
718 Dialogs {
719 id: dialogs
720 objectName: "dialogs"
721 anchors.fill: parent
722 z: overlay.z + 10
723 onPowerOffClicked: {
724 shutdownFadeOutRectangle.enabled = true;
725 shutdownFadeOutRectangle.visible = true;
726 shutdownFadeOut.start();
727 }
728 }
730 Connections {
731 target: SessionBroadcast
732 onShowHome: showHome()
733 }
735 Rectangle {
736 id: shutdownFadeOutRectangle
737 z: screenGrabber.z + 10
738 enabled: false
739 visible: false
740 color: "black"
741 anchors.fill: parent
742 opacity: 0.0
743 NumberAnimation on opacity {
744 id: shutdownFadeOut
745 from: 0.0
746 to: 1.0
747 onStopped: {
748 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
749 DBusUnitySessionService.Shutdown();
750 }
751 }
752 }
753 }