on 5578813 help sign in
unity8/qml/Shell.qml
669 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.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 WindowInputFilter
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 LightDM{id: lightDM} // Provide backend access
150 VolumeControl {
151 id: volumeControl
152 indicators: panel.indicators
153 }
155 DashCommunicator {
156 id: dash
157 objectName: "dashCommunicator"
158 }
160 PhysicalKeysMapper {
161 id: physicalKeysMapper
162 objectName: "physicalKeysMapper"
164 onPowerKeyLongPressed: dialogs.showPowerDialog();
165 onVolumeDownTriggered: volumeControl.volumeDown();
166 onVolumeUpTriggered: volumeControl.volumeUp();
167 onScreenshotTriggered: screenGrabber.capture();
168 }
170 GlobalShortcut {
171 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
172 }
174 WindowInputFilter {
175 id: inputFilter
176 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
177 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
178 }
180 HomeKeyWatcher {
181 onActivated: { launcher.fadeOut(); shell.showHome(); }
182 }
184 Item {
185 id: stages
186 objectName: "stages"
187 width: parent.width
188 height: parent.height
190 Connections {
191 target: ApplicationManager
193 // This signal is also fired when we try to focus the current app
194 // again. We rely on this!
195 onFocusedApplicationIdChanged: {
196 var appId = ApplicationManager.focusedApplicationId;
198 if (wizard.active && appId != "" && appId != "unity8-dash") {
199 // If this happens on first boot, we may be in edge
200 // tutorial or wizard while receiving a call. But a call
201 // is more important than wizard so just bail out of those.
202 tutorial.finish();
203 wizard.hide();
204 }
206 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
207 // If we are in the middle of a call, make dialer lockedApp and show it.
208 // This can happen if user backs out of dialer back to greeter, then
209 // launches dialer again.
210 greeter.lockedApp = appId;
211 }
212 greeter.notifyAppFocused(appId);
214 panel.indicators.hide();
215 }
217 onApplicationAdded: {
218 launcher.hide();
219 }
220 }
222 Loader {
223 id: applicationsDisplayLoader
224 objectName: "applicationsDisplayLoader"
225 anchors.fill: parent
227 // When we have a locked app, we only want to show that one app.
228 // FIXME: do this in a less traumatic way. We currently only allow
229 // locked apps in phone mode (see FIXME in Lockscreen component in
230 // this same file). When that changes, we need to do something
231 // nicer here. But this code is currently just to prevent a
232 // theoretical attack where user enters lockedApp mode, then makes
233 // the screen larger (maybe connects to monitor) and tries to enter
234 // tablet mode.
236 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
237 ? "phone"
238 : shell.usageScenario
239 source: {
240 if(shell.mode === "greeter") {
241 return "Stages/ShimStage.qml"
242 } else if (applicationsDisplayLoader.usageScenario === "phone") {
243 return "Stages/PhoneStage.qml";
244 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
245 return "Stages/TabletStage.qml";
246 } else {
247 return "Stages/DesktopStage.qml";
248 }
249 }
251 property bool interactive: (!greeter || !greeter.shown)
252 && panel.indicators.fullyClosed
253 && launcher.progress == 0
254 && !notifications.useModal
256 onInteractiveChanged: { if (interactive) { focus = true; } }
258 Binding {
259 target: applicationsDisplayLoader.item
260 property: "objectName"
261 value: "stage"
262 }
263 Binding {
264 target: applicationsDisplayLoader.item
265 property: "dragAreaWidth"
266 value: shell.edgeSize
267 }
268 Binding {
269 target: applicationsDisplayLoader.item
270 property: "maximizedAppTopMargin"
271 // Not just using panel.panelHeight as that changes depending on the focused app.
272 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
273 }
274 Binding {
275 target: applicationsDisplayLoader.item
276 property: "interactive"
277 value: applicationsDisplayLoader.interactive
278 }
279 Binding {
280 target: applicationsDisplayLoader.item
281 property: "spreadEnabled"
282 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
283 }
284 Binding {
285 target: applicationsDisplayLoader.item
286 property: "inverseProgress"
287 value: greeter && greeter.locked ? 0 : launcher.progress
288 }
289 Binding {
290 target: applicationsDisplayLoader.item
291 property: "shellOrientationAngle"
292 value: shell.orientationAngle
293 }
294 Binding {
295 target: applicationsDisplayLoader.item
296 property: "shellOrientation"
297 value: shell.orientation
298 }
299 Binding {
300 target: applicationsDisplayLoader.item
301 property: "orientations"
302 value: shell.orientations
303 }
304 Binding {
305 target: applicationsDisplayLoader.item
306 property: "background"
307 value: wallpaperResolver.background
308 }
309 Binding {
310 target: applicationsDisplayLoader.item
311 property: "nativeWidth"
312 value: shell.nativeWidth
313 }
314 Binding {
315 target: applicationsDisplayLoader.item
316 property: "nativeHeight"
317 value: shell.nativeHeight
318 }
319 Binding {
320 target: applicationsDisplayLoader.item
321 property: "beingResized"
322 value: shell.beingResized
323 }
324 Binding {
325 target: applicationsDisplayLoader.item
326 property: "keepDashRunning"
327 value: launcher.shown || launcher.dashSwipe
328 }
329 Binding {
330 target: applicationsDisplayLoader.item
331 property: "suspended"
332 value: greeter.shown
333 }
334 Binding {
335 target: applicationsDisplayLoader.item
336 property: "altTabPressed"
337 value: physicalKeysMapper.altTabPressed
338 }
339 }
340 }
342 InputMethod {
343 id: inputMethod
344 objectName: "inputMethod"
345 anchors { fill: parent; topMargin: panel.panelHeight }
346 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
347 }
349 Connections {
350 target: SessionManager
351 onSessionStopping: {
352 if (!session.parentSession && !session.application) {
353 // nothing is using it. delete it right away
354 session.release();
355 }
356 }
357 }
359 Loader {
360 id: greeterLoader
361 anchors.fill: parent
362 anchors.topMargin: panel.panelHeight
363 sourceComponent: shell.mode != "shell" ? integratedGreeter :
364 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
365 onLoaded: {
366 item.objectName = "greeter"
367 }
368 }
370 Component {
371 id: integratedGreeter
372 Greeter {
374 hides: [launcher, panel.indicators]
375 tabletMode: shell.usageScenario != "phone"
376 launcherOffset: launcher.progress
377 forcedUnlock: wizard.active
378 background: wallpaperResolver.background
380 // avoid overlapping with Launcher's edge drag area
381 // FIXME: Fix TouchRegistry & friends and remove this workaround
382 // Issue involves launcher's DDA getting disabled on a long
383 // left-edge drag
384 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
386 onSessionStarted: {
387 launcher.hide();
388 }
390 onTease: {
391 if (!tutorial.running) {
392 launcher.tease();
393 }
394 }
396 onEmergencyCall: startLockedApp("dialer-app")
397 }
398 }
400 Timer {
401 // See powerConnection for why this is useful
402 id: showGreeterDelayed
403 interval: 1
404 onTriggered: {
405 greeter.forceShow();
406 }
407 }
409 Connections {
410 id: callConnection
411 target: callManager
413 onHasCallsChanged: {
414 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
415 // We just received an incoming call while locked. The
416 // indicator will have already launched dialer-app for us, but
417 // there is a race between "hasCalls" changing and the dialer
418 // starting up. So in case we lose that race, we'll start/
419 // focus the dialer ourselves here too. Even if the indicator
420 // didn't launch the dialer for some reason (or maybe a call
421 // started via some other means), if an active call is
422 // happening, we want to be in the dialer.
423 startLockedApp("dialer-app")
424 }
425 }
426 }
428 Connections {
429 id: powerConnection
430 target: Powerd
432 onStatusChanged: {
433 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
434 !callManager.hasCalls && !wizard.active) {
435 // We don't want to simply call greeter.showNow() here, because
436 // that will take too long. Qt will delay button event
437 // handling until the greeter is done loading and may think the
438 // user held down the power button the whole time, leading to a
439 // power dialog being shown. Instead, delay showing the
440 // greeter until we've finished handling the event. We could
441 // make the greeter load asynchronously instead, but that
442 // introduces a whole host of timing issues, especially with
443 // its animations. So this is simpler.
444 showGreeterDelayed.start();
445 }
446 }
447 }
449 function showHome() {
450 greeter.notifyAboutToFocusApp("unity8-dash");
452 var animate = !lightDM.greeter.active && !stages.shown
453 dash.setCurrentScope(0, animate, false)
454 ApplicationManager.requestFocusApplication("unity8-dash")
455 }
457 function showDash() {
458 if (greeter.notifyShowingDashFromDrag()) {
459 launcher.fadeOut();
460 }
462 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
463 ApplicationManager.requestFocusApplication("unity8-dash")
464 launcher.fadeOut();
465 }
466 }
468 Item {
469 id: overlay
470 z: 10
472 anchors.fill: parent
474 Panel {
475 id: panel
476 objectName: "panel"
477 anchors.fill: parent //because this draws indicator menus
478 indicators {
479 hides: [launcher]
480 available: tutorial.panelEnabled
481 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
482 && (!greeter || !greeter.hasLockedApp)
483 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
485 minimizedPanelHeight: units.gu(3)
486 expandedPanelHeight: units.gu(7)
488 indicatorsModel: Indicators.IndicatorsModel {
489 // tablet and phone both use the same profile
490 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
491 Component.onCompleted: load();
492 }
493 }
495 callHint {
496 greeterShown: greeter.shown
497 }
499 property bool topmostApplicationIsFullscreen:
500 ApplicationManager.focusedApplicationId &&
501 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
503 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
504 || greeter.hasLockedApp
505 locked: greeter && greeter.active
506 }
508 Launcher {
509 id: launcher
510 objectName: "launcher"
512 readonly property bool dashSwipe: progress > 0
514 anchors.top: parent.top
515 anchors.topMargin: inverted ? 0 : panel.panelHeight
516 anchors.bottom: parent.bottom
517 width: parent.width
518 dragAreaWidth: shell.edgeSize
519 available: tutorial.launcherEnabled
520 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
521 && !greeter.hasLockedApp
522 inverted: shell.usageScenario !== "desktop"
524 onShowDashHome: showHome()
525 onDash: showDash()
526 onDashSwipeChanged: {
527 if (dashSwipe) {
528 dash.setCurrentScope(0, false, true)
529 }
530 }
531 onLauncherApplicationSelected: {
532 greeter.notifyAboutToFocusApp(appId);
533 shell.activateApplication(appId);
534 }
535 onShownChanged: {
536 if (shown) {
537 panel.indicators.hide()
538 }
539 }
540 }
542 Tutorial {
543 id: tutorial
544 objectName: "tutorial"
545 anchors.fill: parent
547 paused: callManager.hasCalls || greeter.shown
548 keyboardVisible: inputMethod.state === "shown"
549 usageScenario: shell.usageScenario
550 lastInputTimestamp: inputFilter.lastInputTimestamp
551 launcher: launcher
552 panel: panel
553 stage: applicationsDisplayLoader.item
554 }
556 Wizard {
557 id: wizard
558 objectName: "wizard"
559 anchors.fill: parent
560 background: wallpaperResolver.background
562 function unlockWhenDoneWithWizard() {
563 if (!active) {
564 Connectivity.unlockAllModems();
565 }
566 }
568 Component.onCompleted: unlockWhenDoneWithWizard()
569 onActiveChanged: unlockWhenDoneWithWizard()
570 }
572 Rectangle {
573 id: modalNotificationBackground
575 visible: notifications.useModal
576 color: "#000000"
577 anchors.fill: parent
578 opacity: 0.9
580 MouseArea {
581 anchors.fill: parent
582 }
583 }
585 Notifications {
586 id: notifications
588 model: NotificationBackend.Model
589 margin: units.gu(1)
590 hasMouse: shell.hasMouse
591 background: wallpaperResolver.background
593 y: topmostIsFullscreen ? 0 : panel.panelHeight
594 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
596 states: [
597 State {
598 name: "narrow"
599 when: overlay.width <= units.gu(60)
600 AnchorChanges {
601 target: notifications
602 anchors.left: parent.left
603 anchors.right: parent.right
604 }
605 },
606 State {
607 name: "wide"
608 when: overlay.width > units.gu(60)
609 AnchorChanges {
610 target: notifications
611 anchors.left: undefined
612 anchors.right: parent.right
613 }
614 PropertyChanges { target: notifications; width: units.gu(38) }
615 }
616 ]
617 }
618 }
620 Dialogs {
621 id: dialogs
622 objectName: "dialogs"
623 anchors.fill: parent
624 z: overlay.z + 10
625 usageScenario: shell.usageScenario
626 onPowerOffClicked: {
627 shutdownFadeOutRectangle.enabled = true;
628 shutdownFadeOutRectangle.visible = true;
629 shutdownFadeOut.start();
630 }
631 }
633 Connections {
634 target: SessionBroadcast
635 onShowHome: showHome()
636 }
638 ScreenGrabber {
639 id: screenGrabber
640 rotationAngle: -shell.orientationAngle
641 z: dialogs.z + 10
642 }
644 Cursor {
645 id: cursor
646 visible: shell.hasMouse
647 z: screenGrabber.z + 1
648 }
650 Rectangle {
651 id: shutdownFadeOutRectangle
652 z: cursor.z + 1
653 enabled: false
654 visible: false
655 color: "black"
656 anchors.fill: parent
657 opacity: 0.0
658 NumberAnimation on opacity {
659 id: shutdownFadeOut
660 from: 0.0
661 to: 1.0
662 onStopped: {
663 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
664 DBusUnitySessionService.shutdown();
665 }
666 }
667 }
668 }