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