on 555cc15 help sign in
unity8/qml/Shell.qml
691 lines 24 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 Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Components.Popups 1.0
23import Ubuntu.Gestures 0.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Connectivity 0.1
26import Unity.Launcher 0.1
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
28import Utils 0.1
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
43import Cursor 1.0
46Item {
47 id: shell
49 // to be set from outside
50 property int orientationAngle: 0
51 property int orientation
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 }
66 property bool hasMouse: false
68 // to be read from outside
69 readonly property int mainAppWindowOrientationAngle:
70 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
72 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
73 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
74 && (!greeter || !greeter.animating)
76 readonly property bool showingGreeter: greeter && greeter.shown
78 property bool startingUp: true
79 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
81 property int supportedOrientations: {
82 if (startingUp) {
83 // Ensure we don't rotate during start up
84 return Qt.PrimaryOrientation;
85 } else if (greeter && greeter.shown) {
86 return Qt.PrimaryOrientation;
87 } else if (mainApp) {
88 return mainApp.supportedOrientations;
89 } else {
90 // we just don't care
91 return Qt.PortraitOrientation
92 | Qt.LandscapeOrientation
93 | Qt.InvertedPortraitOrientation
94 | Qt.InvertedLandscapeOrientation;
95 }
96 }
98 // For autopilot consumption
99 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
101 // internal props from here onwards
102 readonly property var mainApp:
103 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
105 // Disable everything while greeter is waiting, so that the user can't swipe
106 // the greeter or launcher until we know whether the session is locked.
107 enabled: greeter && !greeter.waiting
109 property real edgeSize: units.gu(2)
111 WallpaperResolver {
112 id: wallpaperResolver
113 width: shell.width
114 }
116 readonly property alias greeter: greeterLoader.item
118 function activateApplication(appId) {
119 if (ApplicationManager.findApplication(appId)) {
120 ApplicationManager.requestFocusApplication(appId);
121 } else {
122 var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
123 : ApplicationManager.NoFlag;
124 ApplicationManager.startApplication(appId, execFlags);
125 }
126 }
128 function startLockedApp(app) {
129 if (greeter.locked) {
130 greeter.lockedApp = app;
131 }
132 shell.activateApplication(app);
133 }
135 Binding {
136 target: LauncherModel
137 property: "applicationManager"
138 value: ApplicationManager
139 }
141 Component.onCompleted: {
142 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
143 if (ApplicationManager.count > 0) {
144 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
145 }
146 finishStartUpTimer.start();
147 }
149 VolumeControl {
150 id: volumeControl
151 indicators: panel.indicators
152 }
154 DashCommunicator {
155 id: dash
156 objectName: "dashCommunicator"
157 }
159 PhysicalKeysMapper {
160 id: physicalKeysMapper
161 objectName: "physicalKeysMapper"
163 onPowerKeyLongPressed: dialogs.showPowerDialog();
164 onVolumeDownTriggered: volumeControl.volumeDown();
165 onVolumeUpTriggered: volumeControl.volumeUp();
166 onScreenshotTriggered: screenGrabber.capture();
167 }
169 GlobalShortcut {
170 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
171 }
173 WindowKeysFilter {
174 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
175 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
176 }
178 HomeKeyWatcher {
179 onActivated: { launcher.fadeOut(); shell.showHome(); }
180 }
182 Item {
183 id: stages
184 objectName: "stages"
185 width: parent.width
186 height: parent.height
188 Connections {
189 target: ApplicationManager
191 // This signal is also fired when we try to focus the current app
192 // again. We rely on this!
193 onFocusedApplicationIdChanged: {
194 var appId = ApplicationManager.focusedApplicationId;
196 if (tutorial.running && appId != "" && appId != "unity8-dash") {
197 // If this happens on first boot, we may be in edge
198 // tutorial or wizard while receiving a call. But a call
199 // is more important than wizard so just bail out of those.
200 tutorial.finish();
201 wizard.hide();
202 }
204 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
205 // If we are in the middle of a call, make dialer lockedApp and show it.
206 // This can happen if user backs out of dialer back to greeter, then
207 // launches dialer again.
208 greeter.lockedApp = appId;
209 }
210 greeter.notifyAppFocused(appId);
212 panel.indicators.hide();
213 }
215 onApplicationAdded: {
216 launcher.hide();
217 }
218 }
220 Loader {
221 id: applicationsDisplayLoader
222 objectName: "applicationsDisplayLoader"
223 anchors.fill: parent
225 // When we have a locked app, we only want to show that one app.
226 // FIXME: do this in a less traumatic way. We currently only allow
227 // locked apps in phone mode (see FIXME in Lockscreen component in
228 // this same file). When that changes, we need to do something
229 // nicer here. But this code is currently just to prevent a
230 // theoretical attack where user enters lockedApp mode, then makes
231 // the screen larger (maybe connects to monitor) and tries to enter
232 // tablet mode.
234 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
235 ? "phone"
236 : shell.usageScenario
237 source: {
238 if(shell.mode === "greeter") {
239 return "Stages/ShimStage.qml"
240 } else if (applicationsDisplayLoader.usageScenario === "phone") {
241 return "Stages/PhoneStage.qml";
242 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
243 return "Stages/TabletStage.qml";
244 } else {
245 return "Stages/DesktopStage.qml";
246 }
247 }
249 property bool interactive: tutorial.spreadEnabled
250 && (!greeter || !greeter.shown)
251 && panel.indicators.fullyClosed
252 && launcher.progress == 0
253 && !notifications.useModal
255 onInteractiveChanged: { if (interactive) { focus = true; } }
257 Binding {
258 target: applicationsDisplayLoader.item
259 property: "objectName"
260 value: "stage"
261 }
262 Binding {
263 target: applicationsDisplayLoader.item
264 property: "dragAreaWidth"
265 value: shell.edgeSize
266 }
267 Binding {
268 target: applicationsDisplayLoader.item
269 property: "maximizedAppTopMargin"
270 // Not just using panel.panelHeight as that changes depending on the focused app.
271 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
272 }
273 Binding {
274 target: applicationsDisplayLoader.item
275 property: "interactive"
276 value: applicationsDisplayLoader.interactive
277 }
278 Binding {
279 target: applicationsDisplayLoader.item
280 property: "spreadEnabled"
281 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
282 }
283 Binding {
284 target: applicationsDisplayLoader.item
285 property: "inverseProgress"
286 value: greeter && greeter.locked ? 0 : launcher.progress
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "shellOrientationAngle"
291 value: shell.orientationAngle
292 }
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "shellOrientation"
296 value: shell.orientation
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "background"
301 value: wallpaperResolver.background
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "shellPrimaryOrientation"
306 value: shell.primaryOrientation
307 }
308 Binding {
309 target: applicationsDisplayLoader.item
310 property: "nativeOrientation"
311 value: shell.nativeOrientation
312 }
313 Binding {
314 target: applicationsDisplayLoader.item
315 property: "nativeWidth"
316 value: shell.nativeWidth
317 }
318 Binding {
319 target: applicationsDisplayLoader.item
320 property: "nativeHeight"
321 value: shell.nativeHeight
322 }
323 Binding {
324 target: applicationsDisplayLoader.item
325 property: "beingResized"
326 value: shell.beingResized
327 }
328 Binding {
329 target: applicationsDisplayLoader.item
330 property: "keepDashRunning"
331 value: launcher.shown || launcher.dashSwipe
332 }
333 Binding {
334 target: applicationsDisplayLoader.item
335 property: "suspended"
336 value: greeter.shown
337 }
338 Binding {
339 target: applicationsDisplayLoader.item
340 property: "altTabPressed"
341 value: physicalKeysMapper.altTabPressed
342 }
343 }
345 Tutorial {
346 id: tutorial
347 objectName: "tutorial"
348 anchors.fill: parent
350 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
351 // we skip the tutorial on the Desktop to avoid using them. The
352 // Desktop doesn't use the same spread design anyway. The tutorial is
353 // all a bit of a placeholder on non-phone form factors right now.
354 // When the design team gives us more guidance, we can do something
355 // more clever here.
356 active: usageScenario != "desktop" && AccountsService.demoEdges
358 paused: LightDMService.greeter.active
359 launcher: launcher
360 panel: panel
361 edgeSize: shell.edgeSize
363 onFinished: {
364 AccountsService.demoEdges = false;
365 active = false; // for immediate response / if AS is having problems
366 }
367 }
368 }
370 InputMethod {
371 id: inputMethod
372 objectName: "inputMethod"
373 anchors { fill: parent; topMargin: panel.panelHeight }
374 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
375 }
377 Connections {
378 target: SessionManager
379 onSessionStopping: {
380 if (!session.parentSession && !session.application) {
381 // nothing is using it. delete it right away
382 session.release();
383 }
384 }
385 }
387 Loader {
388 id: greeterLoader
389 anchors.fill: parent
390 anchors.topMargin: panel.panelHeight
391 sourceComponent: shell.mode != "shell" ? integratedGreeter :
392 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
393 onLoaded: {
394 item.objectName = "greeter"
395 }
396 }
398 Component {
399 id: integratedGreeter
400 Greeter {
402 hides: [launcher, panel.indicators]
403 tabletMode: shell.usageScenario != "phone"
404 launcherOffset: launcher.progress
405 forcedUnlock: tutorial.running
406 background: wallpaperResolver.background
408 // avoid overlapping with Launcher's edge drag area
409 // FIXME: Fix TouchRegistry & friends and remove this workaround
410 // Issue involves launcher's DDA getting disabled on a long
411 // left-edge drag
412 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
414 onSessionStarted: {
415 launcher.hide();
416 }
418 onTease: {
419 if (!tutorial.running) {
420 launcher.tease();
421 }
422 }
424 onEmergencyCall: startLockedApp("dialer-app")
425 }
426 }
428 Timer {
429 // See powerConnection for why this is useful
430 id: showGreeterDelayed
431 interval: 1
432 onTriggered: {
433 greeter.forceShow();
434 }
435 }
437 Connections {
438 id: callConnection
439 target: callManager
441 onHasCallsChanged: {
442 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
443 // We just received an incoming call while locked. The
444 // indicator will have already launched dialer-app for us, but
445 // there is a race between "hasCalls" changing and the dialer
446 // starting up. So in case we lose that race, we'll start/
447 // focus the dialer ourselves here too. Even if the indicator
448 // didn't launch the dialer for some reason (or maybe a call
449 // started via some other means), if an active call is
450 // happening, we want to be in the dialer.
451 startLockedApp("dialer-app")
452 }
453 }
454 }
456 Connections {
457 id: powerConnection
458 target: Powerd
460 onStatusChanged: {
461 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
462 !callManager.hasCalls && !tutorial.running) {
463 // We don't want to simply call greeter.showNow() here, because
464 // that will take too long. Qt will delay button event
465 // handling until the greeter is done loading and may think the
466 // user held down the power button the whole time, leading to a
467 // power dialog being shown. Instead, delay showing the
468 // greeter until we've finished handling the event. We could
469 // make the greeter load asynchronously instead, but that
470 // introduces a whole host of timing issues, especially with
471 // its animations. So this is simpler.
472 showGreeterDelayed.start();
473 }
474 }
475 }
477 function showHome() {
478 if (tutorial.running) {
479 return
480 }
482 greeter.notifyAboutToFocusApp("unity8-dash");
484 var animate = !LightDMService.greeter.active && !stages.shown
485 dash.setCurrentScope(0, animate, false)
486 ApplicationManager.requestFocusApplication("unity8-dash")
487 }
489 function showDash() {
490 if (greeter.notifyShowingDashFromDrag()) {
491 launcher.fadeOut();
492 }
494 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
495 ApplicationManager.requestFocusApplication("unity8-dash")
496 launcher.fadeOut();
497 }
498 }
500 Item {
501 id: overlay
502 z: 10
504 anchors.fill: parent
506 Panel {
507 id: panel
508 objectName: "panel"
509 anchors.fill: parent //because this draws indicator menus
510 indicators {
511 hides: [launcher]
512 available: tutorial.panelEnabled
513 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
514 && (!greeter || !greeter.hasLockedApp)
515 contentEnabled: tutorial.panelContentEnabled
516 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
518 minimizedPanelHeight: units.gu(3)
519 expandedPanelHeight: units.gu(7)
521 indicatorsModel: Indicators.IndicatorsModel {
522 // tablet and phone both use the same profile
523 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
524 Component.onCompleted: load();
525 }
526 }
528 callHint {
529 greeterShown: greeter.shown
530 }
532 property bool topmostApplicationIsFullscreen:
533 ApplicationManager.focusedApplicationId &&
534 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
536 fullscreenMode: (topmostApplicationIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
537 || greeter.hasLockedApp
538 }
540 Launcher {
541 id: launcher
542 objectName: "launcher"
544 readonly property bool dashSwipe: progress > 0
546 anchors.top: parent.top
547 anchors.topMargin: inverted ? 0 : panel.panelHeight
548 anchors.bottom: parent.bottom
549 width: parent.width
550 dragAreaWidth: shell.edgeSize
551 available: tutorial.launcherEnabled
552 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
553 && !greeter.hasLockedApp
554 inverted: shell.usageScenario !== "desktop"
555 shadeBackground: !tutorial.running
557 onShowDashHome: showHome()
558 onDash: showDash()
559 onDashSwipeChanged: {
560 if (dashSwipe) {
561 dash.setCurrentScope(0, false, true)
562 }
563 }
564 onLauncherApplicationSelected: {
565 if (!tutorial.running) {
566 greeter.notifyAboutToFocusApp(appId);
567 shell.activateApplication(appId)
568 }
569 }
570 onShownChanged: {
571 if (shown) {
572 panel.indicators.hide()
573 }
574 }
575 }
577 Wizard {
578 id: wizard
579 objectName: "wizard"
580 anchors.fill: parent
581 deferred: shell.mode === "greeter"
582 background: wallpaperResolver.background
584 function unlockWhenDoneWithWizard() {
585 if (!active) {
586 Connectivity.unlockAllModems();
587 }
588 }
590 Component.onCompleted: unlockWhenDoneWithWizard()
591 onActiveChanged: unlockWhenDoneWithWizard()
592 }
594 Rectangle {
595 id: modalNotificationBackground
597 visible: notifications.useModal
598 color: "#000000"
599 anchors.fill: parent
600 opacity: 0.9
602 MouseArea {
603 anchors.fill: parent
604 }
605 }
607 Notifications {
608 id: notifications
610 model: NotificationBackend.Model
611 margin: units.gu(1)
612 hasMouse: shell.hasMouse
613 background: wallpaperResolver.background
615 y: topmostIsFullscreen ? 0 : panel.panelHeight
616 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
618 states: [
619 State {
620 name: "narrow"
621 when: overlay.width <= units.gu(60)
622 AnchorChanges {
623 target: notifications
624 anchors.left: parent.left
625 anchors.right: parent.right
626 }
627 },
628 State {
629 name: "wide"
630 when: overlay.width > units.gu(60)
631 AnchorChanges {
632 target: notifications
633 anchors.left: undefined
634 anchors.right: parent.right
635 }
636 PropertyChanges { target: notifications; width: units.gu(38) }
637 }
638 ]
639 }
640 }
642 Dialogs {
643 id: dialogs
644 objectName: "dialogs"
645 anchors.fill: parent
646 z: overlay.z + 10
647 usageScenario: shell.usageScenario
648 onPowerOffClicked: {
649 shutdownFadeOutRectangle.enabled = true;
650 shutdownFadeOutRectangle.visible = true;
651 shutdownFadeOut.start();
652 }
653 }
655 Connections {
656 target: SessionBroadcast
657 onShowHome: showHome()
658 }
660 ScreenGrabber {
661 id: screenGrabber
662 rotationAngle: -shell.orientationAngle
663 z: dialogs.z + 10
664 }
666 Cursor {
667 id: cursor
668 visible: shell.hasMouse
669 z: screenGrabber.z + 1
670 }
672 Rectangle {
673 id: shutdownFadeOutRectangle
674 z: cursor.z + 1
675 enabled: false
676 visible: false
677 color: "black"
678 anchors.fill: parent
679 opacity: 0.0
680 NumberAnimation on opacity {
681 id: shutdownFadeOut
682 from: 0.0
683 to: 1.0
684 onStopped: {
685 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
686 DBusUnitySessionService.shutdown();
687 }
688 }
689 }
690 }