on d4b73c8 help sign in
unity8/qml/Shell.qml
687 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.4
18import QtQuick.Window 2.2
19import AccountsService 0.1
20import Unity.Application 0.1
21import Ubuntu.Components 1.3
22import Ubuntu.Components.Popups 1.3
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 Orientations orientations
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 property bool cursorVisible: false
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 shell.orientations.map(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, currentEventTimestamp);
175 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
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: "orientations"
301 value: shell.orientations
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "background"
306 value: wallpaperResolver.background
307 }
308 Binding {
309 target: applicationsDisplayLoader.item
310 property: "nativeWidth"
311 value: shell.nativeWidth
312 }
313 Binding {
314 target: applicationsDisplayLoader.item
315 property: "nativeHeight"
316 value: shell.nativeHeight
317 }
318 Binding {
319 target: applicationsDisplayLoader.item
320 property: "beingResized"
321 value: shell.beingResized
322 }
323 Binding {
324 target: applicationsDisplayLoader.item
325 property: "keepDashRunning"
326 value: launcher.shown || launcher.dashSwipe
327 }
328 Binding {
329 target: applicationsDisplayLoader.item
330 property: "suspended"
331 value: greeter.shown
332 }
333 Binding {
334 target: applicationsDisplayLoader.item
335 property: "altTabPressed"
336 value: physicalKeysMapper.altTabPressed
337 }
338 }
340 Tutorial {
341 id: tutorial
342 objectName: "tutorial"
343 anchors.fill: parent
345 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
346 // we skip the tutorial on the Desktop to avoid using them. The
347 // Desktop doesn't use the same spread design anyway. The tutorial is
348 // all a bit of a placeholder on non-phone form factors right now.
349 // When the design team gives us more guidance, we can do something
350 // more clever here.
351 active: usageScenario != "desktop" && AccountsService.demoEdges
353 paused: LightDMService.greeter.active
354 launcher: launcher
355 panel: panel
356 edgeSize: shell.edgeSize
358 onFinished: {
359 AccountsService.demoEdges = false;
360 active = false; // for immediate response / if AS is having problems
361 }
362 }
363 }
365 InputMethod {
366 id: inputMethod
367 objectName: "inputMethod"
368 anchors { fill: parent; topMargin: panel.panelHeight }
369 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
370 }
372 Connections {
373 target: SessionManager
374 onSessionStopping: {
375 if (!session.parentSession && !session.application) {
376 // nothing is using it. delete it right away
377 session.release();
378 }
379 }
380 }
382 Loader {
383 id: greeterLoader
384 anchors.fill: parent
385 anchors.topMargin: panel.panelHeight
386 sourceComponent: shell.mode != "shell" ? integratedGreeter :
387 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
388 onLoaded: {
389 item.objectName = "greeter"
390 }
391 }
393 Component {
394 id: integratedGreeter
395 Greeter {
397 hides: [launcher, panel.indicators]
398 tabletMode: shell.usageScenario != "phone"
399 launcherOffset: launcher.progress
400 forcedUnlock: tutorial.running
401 background: wallpaperResolver.background
403 // avoid overlapping with Launcher's edge drag area
404 // FIXME: Fix TouchRegistry & friends and remove this workaround
405 // Issue involves launcher's DDA getting disabled on a long
406 // left-edge drag
407 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
409 onSessionStarted: {
410 launcher.hide();
411 }
413 onTease: {
414 if (!tutorial.running) {
415 launcher.tease();
416 }
417 }
419 onEmergencyCall: startLockedApp("dialer-app")
420 }
421 }
423 Timer {
424 // See powerConnection for why this is useful
425 id: showGreeterDelayed
426 interval: 1
427 onTriggered: {
428 greeter.forceShow();
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 = !LightDMService.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 || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
509 && (!greeter || !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 topmostApplicationIsFullscreen:
528 ApplicationManager.focusedApplicationId &&
529 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
531 fullscreenMode: (topmostApplicationIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
532 || greeter.hasLockedApp
533 locked: greeter && greeter.active
534 }
536 Launcher {
537 id: launcher
538 objectName: "launcher"
540 readonly property bool dashSwipe: progress > 0
542 anchors.top: parent.top
543 anchors.topMargin: inverted ? 0 : panel.panelHeight
544 anchors.bottom: parent.bottom
545 width: parent.width
546 dragAreaWidth: shell.edgeSize
547 available: tutorial.launcherEnabled
548 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
549 && !greeter.hasLockedApp
550 inverted: shell.usageScenario !== "desktop"
551 shadeBackground: !tutorial.running
553 onShowDashHome: showHome()
554 onDash: showDash()
555 onDashSwipeChanged: {
556 if (dashSwipe) {
557 dash.setCurrentScope(0, false, true)
558 }
559 }
560 onLauncherApplicationSelected: {
561 if (!tutorial.running) {
562 greeter.notifyAboutToFocusApp(appId);
563 shell.activateApplication(appId)
564 }
565 }
566 onShownChanged: {
567 if (shown) {
568 panel.indicators.hide()
569 }
570 }
571 }
573 Wizard {
574 id: wizard
575 objectName: "wizard"
576 anchors.fill: parent
577 deferred: shell.mode === "greeter"
578 background: wallpaperResolver.background
580 function unlockWhenDoneWithWizard() {
581 if (!active) {
582 Connectivity.unlockAllModems();
583 }
584 }
586 Component.onCompleted: unlockWhenDoneWithWizard()
587 onActiveChanged: unlockWhenDoneWithWizard()
588 }
590 Rectangle {
591 id: modalNotificationBackground
593 visible: notifications.useModal
594 color: "#000000"
595 anchors.fill: parent
596 opacity: 0.9
598 MouseArea {
599 anchors.fill: parent
600 }
601 }
603 Notifications {
604 id: notifications
606 model: NotificationBackend.Model
607 margin: units.gu(1)
608 hasMouse: shell.hasMouse
609 background: wallpaperResolver.background
611 y: topmostIsFullscreen ? 0 : panel.panelHeight
612 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
614 states: [
615 State {
616 name: "narrow"
617 when: overlay.width <= units.gu(60)
618 AnchorChanges {
619 target: notifications
620 anchors.left: parent.left
621 anchors.right: parent.right
622 }
623 },
624 State {
625 name: "wide"
626 when: overlay.width > units.gu(60)
627 AnchorChanges {
628 target: notifications
629 anchors.left: undefined
630 anchors.right: parent.right
631 }
632 PropertyChanges { target: notifications; width: units.gu(38) }
633 }
634 ]
635 }
636 }
638 Dialogs {
639 id: dialogs
640 objectName: "dialogs"
641 anchors.fill: parent
642 z: overlay.z + 10
643 usageScenario: shell.usageScenario
644 onPowerOffClicked: {
645 shutdownFadeOutRectangle.enabled = true;
646 shutdownFadeOutRectangle.visible = true;
647 shutdownFadeOut.start();
648 }
649 }
651 Connections {
652 target: SessionBroadcast
653 onShowHome: showHome()
654 }
656 ScreenGrabber {
657 id: screenGrabber
658 rotationAngle: -shell.orientationAngle
659 z: dialogs.z + 10
660 }
662 Cursor {
663 id: cursor
664 visible: shell.hasMouse
665 z: screenGrabber.z + 1
666 }
668 Rectangle {
669 id: shutdownFadeOutRectangle
670 z: cursor.z + 1
671 enabled: false
672 visible: false
673 color: "black"
674 anchors.fill: parent
675 opacity: 0.0
676 NumberAnimation on opacity {
677 id: shutdownFadeOut
678 from: 0.0
679 to: 1.0
680 onStopped: {
681 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
682 DBusUnitySessionService.shutdown();
683 }
684 }
685 }
686 }