on 337d819 help sign in
unity8/qml/Shell.qml
675 lines 23 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.Launcher 0.1
27import Utils 0.1
28import LightDM 0.1 as LightDM
29import Powerd 0.1
30import SessionBroadcast 0.1
31import "Greeter"
32import "Launcher"
33import "Panel"
34import "Components"
35import "Notifications"
36import "Stages"
37import "Tutorial"
38import "Wizard"
39import Unity.Notifications 1.0 as NotificationBackend
40import Unity.Session 0.1
41import Unity.DashCommunicator 0.1
42import Unity.Indicators 0.1 as Indicators
44Item {
45 id: shell
47 // to be set from outside
48 property int orientationAngle: 0
49 property int orientation
50 property int primaryOrientation
51 property int nativeOrientation
52 property real nativeWidth
53 property real nativeHeight
54 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
55 property bool beingResized
56 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
57 function updateFocusedAppOrientation() {
58 applicationsDisplayLoader.item.updateFocusedAppOrientation();
59 }
60 function updateFocusedAppOrientationAnimated() {
61 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
62 }
64 // to be read from outside
65 readonly property int mainAppWindowOrientationAngle:
66 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
68 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
69 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
70 && !greeter.animating
72 property bool startingUp: true
73 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
75 property int supportedOrientations: {
76 if (startingUp) {
77 // Ensure we don't rotate during start up
78 return Qt.PrimaryOrientation;
79 } else if (greeter.shown) {
80 return Qt.PrimaryOrientation;
81 } else if (mainApp) {
82 return mainApp.supportedOrientations;
83 } else {
84 // we just don't care
85 return Qt.PortraitOrientation
86 | Qt.LandscapeOrientation
87 | Qt.InvertedPortraitOrientation
88 | Qt.InvertedLandscapeOrientation;
89 }
90 }
92 // For autopilot consumption
93 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
95 // internal props from here onwards
96 readonly property var mainApp:
97 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
99 // Disable everything while greeter is waiting, so that the user can't swipe
100 // the greeter or launcher until we know whether the session is locked.
101 enabled: !greeter.waiting
103 property real edgeSize: units.gu(2)
104 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
105 property url background: asImageTester.status == Image.Ready ? asImageTester.source
106 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
107 readonly property real panelHeight: panel.panelHeight
109 function activateApplication(appId) {
110 if (ApplicationManager.findApplication(appId)) {
111 ApplicationManager.requestFocusApplication(appId);
112 } else {
113 var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
114 : ApplicationManager.NoFlag;
115 ApplicationManager.startApplication(appId, execFlags);
116 }
117 }
119 function startLockedApp(app) {
120 if (greeter.locked) {
121 greeter.lockedApp = app;
122 }
123 shell.activateApplication(app);
124 }
126 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
127 Image {
128 id: asImageTester
129 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
130 height: 0
131 width: 0
132 sourceSize.height: 0
133 sourceSize.width: 0
134 }
136 GSettings {
137 id: backgroundSettings
138 objectName: "backgroundSettings"
139 schema.id: "org.gnome.desktop.background"
140 }
142 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
143 Image {
144 id: gsImageTester
145 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
146 height: 0
147 width: 0
148 sourceSize.height: 0
149 sourceSize.width: 0
150 }
152 Binding {
153 target: LauncherModel
154 property: "applicationManager"
155 value: ApplicationManager
156 }
158 Component.onCompleted: {
159 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
160 if (ApplicationManager.count > 0) {
161 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
162 }
163 finishStartUpTimer.start();
164 }
166 VolumeControl {
167 id: volumeControl
168 }
170 DashCommunicator {
171 id: dash
172 objectName: "dashCommunicator"
173 }
175 PhysicalKeysMapper {
176 id: physicalKeysMapper
178 onPowerKeyLongPressed: dialogs.showPowerDialog()
179 onVolumeDownTriggered: volumeControl.volumeDown();
180 onVolumeUpTriggered: volumeControl.volumeUp();
181 onScreenshotTriggered: screenGrabber.capture();
182 }
184 ScreenGrabber {
185 id: screenGrabber
186 z: dialogs.z + 10
187 enabled: Powerd.status === Powerd.On
188 }
190 Binding {
191 target: ApplicationManager
192 property: "forceDashActive"
193 value: launcher.shown || launcher.dashSwipe
194 }
196 WindowKeysFilter {
197 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
198 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
199 }
201 Item {
202 id: stages
203 objectName: "stages"
204 width: parent.width
205 height: parent.height
206 visible: !ApplicationManager.empty
208 Connections {
209 target: ApplicationManager
211 // This signal is also fired when we try to focus the current app
212 // again. We rely on this!
213 onFocusedApplicationIdChanged: {
214 var appId = ApplicationManager.focusedApplicationId;
216 if (tutorial.running && appId != "unity8-dash") {
217 // If this happens on first boot, we may be in edge
218 // tutorial or wizard while receiving a call. But a call
219 // is more important than wizard so just bail out of those.
220 tutorial.finish();
221 wizard.hide();
222 }
224 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
225 // If we are in the middle of a call, make dialer lockedApp and show it.
226 // This can happen if user backs out of dialer back to greeter, then
227 // launches dialer again.
228 greeter.lockedApp = appId;
229 }
230 greeter.notifyAppFocused(appId);
232 panel.indicators.hide();
233 }
235 onApplicationAdded: {
236 launcher.hide();
237 }
238 }
240 Loader {
241 id: applicationsDisplayLoader
242 objectName: "applicationsDisplayLoader"
243 anchors.fill: parent
245 // When we have a locked app, we only want to show that one app.
246 // FIXME: do this in a less traumatic way. We currently only allow
247 // locked apps in phone mode (see FIXME in Lockscreen component in
248 // this same file). When that changes, we need to do something
249 // nicer here. But this code is currently just to prevent a
250 // theoretical attack where user enters lockedApp mode, then makes
251 // the screen larger (maybe connects to monitor) and tries to enter
252 // tablet mode.
254 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
255 ? "phone"
256 : shell.usageScenario
257 source: {
258 if (applicationsDisplayLoader.usageScenario === "phone") {
259 return "Stages/PhoneStage.qml";
260 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
261 return "Stages/TabletStage.qml";
262 } else {
263 return "Stages/DesktopStage.qml";
264 }
265 }
267 property bool interactive: tutorial.spreadEnabled
268 && !greeter.shown
269 && panel.indicators.fullyClosed
270 && launcher.progress == 0
271 && !notifications.useModal
273 onInteractiveChanged: { if (interactive) { focus = true; } }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "objectName"
278 value: "stage"
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "dragAreaWidth"
283 value: shell.edgeSize
284 }
285 Binding {
286 target: applicationsDisplayLoader.item
287 property: "maximizedAppTopMargin"
288 // Not just using panel.panelHeight as that changes depending on the focused app.
289 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
290 }
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "interactive"
294 value: applicationsDisplayLoader.interactive
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "spreadEnabled"
299 value: tutorial.spreadEnabled && !greeter.hasLockedApp
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "inverseProgress"
304 value: greeter.locked ? 0 : launcher.progress
305 }
306 Binding {
307 target: applicationsDisplayLoader.item
308 property: "shellOrientationAngle"
309 value: shell.orientationAngle
310 }
311 Binding {
312 target: applicationsDisplayLoader.item
313 property: "shellOrientation"
314 value: shell.orientation
315 }
316 Binding {
317 target: applicationsDisplayLoader.item
318 property: "background"
319 value: shell.background
320 }
321 Binding {
322 target: applicationsDisplayLoader.item
323 property: "shellPrimaryOrientation"
324 value: shell.primaryOrientation
325 }
326 Binding {
327 target: applicationsDisplayLoader.item
328 property: "nativeOrientation"
329 value: shell.nativeOrientation
330 }
331 Binding {
332 target: applicationsDisplayLoader.item
333 property: "nativeWidth"
334 value: shell.nativeWidth
335 }
336 Binding {
337 target: applicationsDisplayLoader.item
338 property: "nativeHeight"
339 value: shell.nativeHeight
340 }
341 Binding {
342 target: applicationsDisplayLoader.item
343 property: "beingResized"
344 value: shell.beingResized
345 }
346 }
347 }
349 InputMethod {
350 id: inputMethod
351 objectName: "inputMethod"
352 anchors { fill: parent; topMargin: panel.panelHeight }
353 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
354 }
356 Connections {
357 target: SurfaceManager
358 onSurfaceCreated: {
359 if (surface.type == MirSurfaceItem.InputMethod) {
360 inputMethod.surface = surface;
361 }
362 }
364 onSurfaceDestroyed: {
365 if (inputMethod.surface == surface) {
366 inputMethod.surface = null;
367 surface.parent = null;
368 }
369 if (!surface.parent) {
370 // there's no one displaying it. delete it right away
371 surface.release();
372 }
373 }
374 }
375 Connections {
376 target: SessionManager
377 onSessionStopping: {
378 if (!session.parentSession && !session.application) {
379 // nothing is using it. delete it right away
380 session.release();
381 }
382 }
383 }
385 Greeter {
386 id: greeter
387 objectName: "greeter"
389 hides: [launcher, panel.indicators]
390 tabletMode: shell.usageScenario !== "phone"
391 launcherOffset: launcher.progress
392 forcedUnlock: tutorial.running
393 background: shell.background
395 anchors.fill: parent
396 anchors.topMargin: panel.panelHeight
398 // avoid overlapping with Launcher's edge drag area
399 // FIXME: Fix TouchRegistry & friends and remove this workaround
400 // Issue involves launcher's DDA getting disabled on a long
401 // left-edge drag
402 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
404 onSessionStarted: {
405 launcher.hide();
406 }
408 onTease: {
409 if (!tutorial.running) {
410 launcher.tease();
411 }
412 }
414 onEmergencyCall: startLockedApp("dialer-app")
416 Timer {
417 // See powerConnection for why this is useful
418 id: showGreeterDelayed
419 interval: 1
420 onTriggered: {
421 greeter.forceShow();
422 }
423 }
425 Binding {
426 target: ApplicationManager
427 property: "suspended"
428 value: greeter.shown
429 }
430 }
432 Connections {
433 id: callConnection
434 target: callManager
436 onHasCallsChanged: {
437 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
438 // We just received an incoming call while locked. The
439 // indicator will have already launched dialer-app for us, but
440 // there is a race between "hasCalls" changing and the dialer
441 // starting up. So in case we lose that race, we'll start/
442 // focus the dialer ourselves here too. Even if the indicator
443 // didn't launch the dialer for some reason (or maybe a call
444 // started via some other means), if an active call is
445 // happening, we want to be in the dialer.
446 startLockedApp("dialer-app")
447 }
448 }
449 }
451 Connections {
452 id: powerConnection
453 target: Powerd
455 onStatusChanged: {
456 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
457 !callManager.hasCalls && !tutorial.running) {
458 // We don't want to simply call greeter.showNow() here, because
459 // that will take too long. Qt will delay button event
460 // handling until the greeter is done loading and may think the
461 // user held down the power button the whole time, leading to a
462 // power dialog being shown. Instead, delay showing the
463 // greeter until we've finished handling the event. We could
464 // make the greeter load asynchronously instead, but that
465 // introduces a whole host of timing issues, especially with
466 // its animations. So this is simpler.
467 showGreeterDelayed.start();
468 }
469 }
470 }
472 function showHome() {
473 if (tutorial.running) {
474 return
475 }
477 greeter.notifyAboutToFocusApp("unity8-dash");
479 var animate = !LightDM.Greeter.active && !stages.shown
480 dash.setCurrentScope(0, animate, false)
481 ApplicationManager.requestFocusApplication("unity8-dash")
482 }
484 function showDash() {
485 if (greeter.notifyShowingDashFromDrag()) {
486 launcher.fadeOut();
487 }
489 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
490 ApplicationManager.requestFocusApplication("unity8-dash")
491 launcher.fadeOut();
492 }
493 }
495 Item {
496 id: overlay
497 z: 10
499 anchors.fill: parent
501 Panel {
502 id: panel
503 objectName: "panel"
504 anchors.fill: parent //because this draws indicator menus
505 indicators {
506 hides: [launcher]
507 available: tutorial.panelEnabled
508 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
509 && !greeter.hasLockedApp
510 contentEnabled: tutorial.panelContentEnabled
511 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
513 minimizedPanelHeight: units.gu(3)
514 expandedPanelHeight: units.gu(7)
516 indicatorsModel: Indicators.IndicatorsModel {
517 // tablet and phone both use the same profile
518 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
519 Component.onCompleted: load();
520 }
521 }
523 callHint {
524 greeterShown: greeter.shown
525 }
527 property bool mainAppIsFullscreen: shell.mainApp && shell.mainApp.fullscreen
528 fullscreenMode: (mainAppIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
529 || greeter.hasLockedApp
530 }
532 Launcher {
533 id: launcher
534 objectName: "launcher"
536 readonly property bool dashSwipe: progress > 0
538 anchors.top: parent.top
539 anchors.topMargin: inverted ? 0 : panel.panelHeight
540 anchors.bottom: parent.bottom
541 width: parent.width
542 dragAreaWidth: shell.edgeSize
543 available: tutorial.launcherEnabled
544 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
545 && !greeter.hasLockedApp
546 inverted: shell.usageScenario !== "desktop"
547 shadeBackground: !tutorial.running
549 onShowDashHome: showHome()
550 onDash: showDash()
551 onDashSwipeChanged: {
552 if (dashSwipe) {
553 dash.setCurrentScope(0, false, true)
554 }
555 }
556 onLauncherApplicationSelected: {
557 if (!tutorial.running) {
558 greeter.notifyAboutToFocusApp(appId);
559 shell.activateApplication(appId)
560 }
561 }
562 onShownChanged: {
563 if (shown) {
564 panel.indicators.hide()
565 }
566 }
567 }
569 Wizard {
570 id: wizard
571 anchors.fill: parent
572 background: shell.background
573 }
575 Rectangle {
576 id: modalNotificationBackground
578 visible: notifications.useModal
579 color: "#000000"
580 anchors.fill: parent
581 opacity: 0.9
583 MouseArea {
584 anchors.fill: parent
585 }
586 }
588 Notifications {
589 id: notifications
591 model: NotificationBackend.Model
592 margin: units.gu(1)
594 y: topmostIsFullscreen ? 0 : panel.panelHeight
595 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
597 states: [
598 State {
599 name: "narrow"
600 when: overlay.width <= units.gu(60)
601 AnchorChanges {
602 target: notifications
603 anchors.left: parent.left
604 anchors.right: parent.right
605 }
606 },
607 State {
608 name: "wide"
609 when: overlay.width > units.gu(60)
610 AnchorChanges {
611 target: notifications
612 anchors.left: undefined
613 anchors.right: parent.right
614 }
615 PropertyChanges { target: notifications; width: units.gu(38) }
616 }
617 ]
618 }
619 }
621 Dialogs {
622 id: dialogs
623 objectName: "dialogs"
624 anchors.fill: parent
625 z: overlay.z + 10
626 onPowerOffClicked: {
627 shutdownFadeOutRectangle.enabled = true;
628 shutdownFadeOutRectangle.visible = true;
629 shutdownFadeOut.start();
630 }
631 }
633 Tutorial {
634 id: tutorial
635 objectName: "tutorial"
636 active: AccountsService.demoEdges
637 paused: LightDM.Greeter.active
638 launcher: launcher
639 panel: panel
640 stages: stages
641 overlay: overlay
642 edgeSize: shell.edgeSize
644 onFinished: {
645 AccountsService.demoEdges = false;
646 active = false; // for immediate response / if AS is having problems
647 }
648 }
650 Connections {
651 target: SessionBroadcast
652 onShowHome: showHome()
653 }
655 Rectangle {
656 id: shutdownFadeOutRectangle
657 z: screenGrabber.z + 10
658 enabled: false
659 visible: false
660 color: "black"
661 anchors.fill: parent
662 opacity: 0.0
663 NumberAnimation on opacity {
664 id: shutdownFadeOut
665 from: 0.0
666 to: 1.0
667 onStopped: {
668 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
669 DBusUnitySessionService.Shutdown();
670 }
671 }
672 }
673 }