on 2258785 help sign in
unity8/qml/Shell.qml
639 lines 22 kB history raw
1/*
2 * Copyright (C) 2013 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 // Disable everything while greeter is waiting, so that the user can't swipe
49 // the greeter or launcher until we know whether the session is locked.
50 enabled: !greeter.waiting
52 // this is only here to select the width / height of the window if not running fullscreen
53 property bool tablet: false
54 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
55 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
57 property real edgeSize: units.gu(2)
58 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
59 property url background: asImageTester.status == Image.Ready ? asImageTester.source
60 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
61 readonly property real panelHeight: panel.panelHeight
63 property bool sideStageEnabled: shell.width >= units.gu(100)
64 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
66 property int orientation
67 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
68 onDeviceOrientationAngleChanged: {
69 if (!OrientationLock.enabled) {
70 orientation = Screen.orientation;
71 }
72 }
73 readonly property bool orientationLockEnabled: OrientationLock.enabled
74 onOrientationLockEnabledChanged: {
75 if (orientationLockEnabled) {
76 OrientationLock.savedOrientation = Screen.orientation;
77 } else {
78 orientation = Screen.orientation;
79 }
80 }
82 // This is _only_ used to expose the property to autopilot tests
83 readonly property string testShellMode: shellMode
85 function activateApplication(appId) {
86 if (ApplicationManager.findApplication(appId)) {
87 ApplicationManager.requestFocusApplication(appId);
88 } else {
89 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
90 ApplicationManager.startApplication(appId, execFlags);
91 }
92 }
94 function startLockedApp(app) {
95 if (greeter.locked) {
96 greeter.lockedApp = app;
97 }
98 shell.activateApplication(app);
99 }
101 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
102 Image {
103 id: asImageTester
104 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
105 height: 0
106 width: 0
107 sourceSize.height: 0
108 sourceSize.width: 0
109 }
111 GSettings {
112 id: backgroundSettings
113 objectName: "backgroundSettings"
114 schema.id: "org.gnome.desktop.background"
115 }
117 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
118 Image {
119 id: gsImageTester
120 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
121 height: 0
122 width: 0
123 sourceSize.height: 0
124 sourceSize.width: 0
125 }
127 GSettings {
128 id: usageModeSettings
129 schema.id: "com.canonical.Unity8"
130 }
132 Binding {
133 target: LauncherModel
134 property: "applicationManager"
135 value: ApplicationManager
136 }
138 Component.onCompleted: {
139 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
140 if (ApplicationManager.count > 0) {
141 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
142 }
143 if (orientationLockEnabled) {
144 orientation = OrientationLock.savedOrientation;
145 }
146 }
148 VolumeControl {
149 id: volumeControl
150 }
152 DashCommunicator {
153 id: dash
154 objectName: "dashCommunicator"
155 }
157 PhysicalKeysMapper {
158 id: physicalKeysMapper
160 onPowerKeyLongPressed: dialogs.showPowerDialog()
161 onVolumeDownTriggered: volumeControl.volumeDown();
162 onVolumeUpTriggered: volumeControl.volumeUp();
163 onScreenshotTriggered: screenGrabber.capture();
164 }
166 ScreenGrabber {
167 id: screenGrabber
168 z: dialogs.z + 10
169 enabled: Powerd.status === Powerd.On
170 }
172 Binding {
173 target: ApplicationManager
174 property: "forceDashActive"
175 value: launcher.shown || launcher.dashSwipe
176 }
178 WindowKeysFilter {
179 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
180 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
181 }
183 HomeKeyWatcher {
184 onActivated: { launcher.fadeOut(); shell.showHome(); }
185 }
187 Item {
188 id: stages
189 objectName: "stages"
190 width: parent.width
191 height: parent.height
193 Connections {
194 target: ApplicationManager
196 // This signal is also fired when we try to focus the current app
197 // again. We rely on this!
198 onFocusedApplicationIdChanged: {
199 var appId = ApplicationManager.focusedApplicationId;
201 if (tutorial.running && appId != "" && appId != "unity8-dash") {
202 // If this happens on first boot, we may be in edge
203 // tutorial or wizard while receiving a call. But a call
204 // is more important than wizard so just bail out of those.
205 tutorial.finish();
206 wizard.hide();
207 }
209 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
210 // If we are in the middle of a call, make dialer lockedApp and show it.
211 // This can happen if user backs out of dialer back to greeter, then
212 // launches dialer again.
213 greeter.lockedApp = appId;
214 }
215 greeter.notifyAppFocused(appId);
217 panel.indicators.hide();
218 }
220 onApplicationAdded: {
221 launcher.hide();
222 }
223 }
225 Loader {
226 id: applicationsDisplayLoader
227 objectName: "applicationsDisplayLoader"
228 anchors.fill: parent
230 // When we have a locked app, we only want to show that one app.
231 // FIXME: do this in a less traumatic way. We currently only allow
232 // locked apps in phone mode (see FIXME in Lockscreen component in
233 // this same file). When that changes, we need to do something
234 // nicer here. But this code is currently just to prevent a
235 // theoretical attack where user enters lockedApp mode, then makes
236 // the screen larger (maybe connects to monitor) and tries to enter
237 // tablet mode.
238 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
239 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
240 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
242 property bool interactive: tutorial.spreadEnabled
243 && !greeter.shown
244 && panel.indicators.fullyClosed
245 && launcher.progress == 0
246 && !notifications.useModal
248 onInteractiveChanged: { if (interactive) { focus = true; } }
250 Binding {
251 target: applicationsDisplayLoader.item
252 property: "objectName"
253 value: "stage"
254 }
255 Binding {
256 target: applicationsDisplayLoader.item
257 property: "dragAreaWidth"
258 value: shell.edgeSize
259 }
260 Binding {
261 target: applicationsDisplayLoader.item
262 property: "maximizedAppTopMargin"
263 // Not just using panel.panelHeight as that changes depending on the focused app.
264 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
265 }
266 Binding {
267 target: applicationsDisplayLoader.item
268 property: "interactive"
269 value: applicationsDisplayLoader.interactive
270 }
271 Binding {
272 target: applicationsDisplayLoader.item
273 property: "spreadEnabled"
274 value: tutorial.spreadEnabled && !greeter.hasLockedApp
275 }
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "inverseProgress"
279 value: greeter.locked ? 0 : launcher.progress
280 }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "orientation"
284 value: shell.orientation
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "background"
289 value: shell.background
290 }
291 }
293 Tutorial {
294 id: tutorial
295 objectName: "tutorial"
296 anchors.fill: parent
297 active: AccountsService.demoEdges
298 paused: LightDM.Greeter.active
299 launcher: launcher
300 panel: panel
301 edgeSize: shell.edgeSize
303 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
304 // we'll tell the tutorial to avoid using them on the Desktop. The
305 // Desktop doesn't use the same spread design anyway. The tutorial is
306 // all a bit of a placeholder on non-phone form factors right now.
307 // When the design team gives us more guidance, we can do something
308 // more clever here.
309 // TODO: use DeviceConfiguration instead of checking source
310 useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
312 onFinished: {
313 AccountsService.demoEdges = false;
314 active = false; // for immediate response / if AS is having problems
315 }
316 }
317 }
319 InputMethod {
320 id: inputMethod
321 objectName: "inputMethod"
322 anchors { fill: parent; topMargin: panel.panelHeight }
323 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
324 }
326 Connections {
327 target: SurfaceManager
328 onSurfaceCreated: {
329 if (surface.type == MirSurfaceItem.InputMethod) {
330 inputMethod.surface = surface;
331 }
332 }
334 onSurfaceDestroyed: {
335 if (inputMethod.surface == surface) {
336 inputMethod.surface = null;
337 surface.parent = null;
338 }
339 if (!surface.parent) {
340 // there's no one displaying it. delete it right away
341 surface.release();
342 }
343 }
344 }
345 Connections {
346 target: SessionManager
347 onSessionStopping: {
348 if (!session.parentSession && !session.application) {
349 // nothing is using it. delete it right away
350 session.release();
351 }
352 }
353 }
355 Greeter {
356 id: greeter
357 objectName: "greeter"
359 hides: [launcher, panel.indicators]
360 tabletMode: shell.sideStageEnabled
361 launcherOffset: launcher.progress
362 forcedUnlock: tutorial.running
363 background: shell.background
365 anchors.fill: parent
366 anchors.topMargin: panel.panelHeight
368 // avoid overlapping with Launcher's edge drag area
369 // FIXME: Fix TouchRegistry & friends and remove this workaround
370 // Issue involves launcher's DDA getting disabled on a long
371 // left-edge drag
372 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
374 onSessionStarted: {
375 launcher.hide();
376 }
378 onTease: {
379 if (!tutorial.running) {
380 launcher.tease();
381 }
382 }
384 onEmergencyCall: startLockedApp("dialer-app")
386 Timer {
387 // See powerConnection for why this is useful
388 id: showGreeterDelayed
389 interval: 1
390 onTriggered: {
391 greeter.forceShow();
392 }
393 }
395 Binding {
396 target: ApplicationManager
397 property: "suspended"
398 value: greeter.shown
399 }
400 }
402 Connections {
403 id: callConnection
404 target: callManager
406 onHasCallsChanged: {
407 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
408 // We just received an incoming call while locked. The
409 // indicator will have already launched dialer-app for us, but
410 // there is a race between "hasCalls" changing and the dialer
411 // starting up. So in case we lose that race, we'll start/
412 // focus the dialer ourselves here too. Even if the indicator
413 // didn't launch the dialer for some reason (or maybe a call
414 // started via some other means), if an active call is
415 // happening, we want to be in the dialer.
416 startLockedApp("dialer-app")
417 }
418 }
419 }
421 Connections {
422 id: powerConnection
423 target: Powerd
425 onStatusChanged: {
426 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
427 !callManager.hasCalls && !tutorial.running) {
428 // We don't want to simply call greeter.showNow() here, because
429 // that will take too long. Qt will delay button event
430 // handling until the greeter is done loading and may think the
431 // user held down the power button the whole time, leading to a
432 // power dialog being shown. Instead, delay showing the
433 // greeter until we've finished handling the event. We could
434 // make the greeter load asynchronously instead, but that
435 // introduces a whole host of timing issues, especially with
436 // its animations. So this is simpler.
437 showGreeterDelayed.start();
438 }
439 }
440 }
442 function showHome() {
443 if (tutorial.running) {
444 return
445 }
447 greeter.notifyAboutToFocusApp("unity8-dash");
449 var animate = !LightDM.Greeter.active && !stages.shown
450 dash.setCurrentScope(0, animate, false)
451 ApplicationManager.requestFocusApplication("unity8-dash")
452 }
454 function showDash() {
455 if (greeter.notifyShowingDashFromDrag()) {
456 launcher.fadeOut();
457 }
459 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
460 ApplicationManager.requestFocusApplication("unity8-dash")
461 launcher.fadeOut();
462 }
463 }
465 Item {
466 id: overlay
467 z: 10
469 anchors.fill: parent
471 Panel {
472 id: panel
473 objectName: "panel"
474 anchors.fill: parent //because this draws indicator menus
475 indicators {
476 hides: [launcher]
477 available: tutorial.panelEnabled
478 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
479 && !greeter.hasLockedApp
480 contentEnabled: tutorial.panelContentEnabled
481 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
483 minimizedPanelHeight: units.gu(3)
484 expandedPanelHeight: units.gu(7)
486 indicatorsModel: Indicators.IndicatorsModel {
487 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
488 profile: indicatorProfile
489 Component.onCompleted: load()
490 }
491 }
492 callHint {
493 greeterShown: greeter.shown
494 }
496 property bool topmostApplicationIsFullscreen:
497 ApplicationManager.focusedApplicationId &&
498 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
500 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
501 || greeter.hasLockedApp
502 }
504 Launcher {
505 id: launcher
506 objectName: "launcher"
508 readonly property bool dashSwipe: progress > 0
510 anchors.top: parent.top
511 anchors.topMargin: inverted ? 0 : panel.panelHeight
512 anchors.bottom: parent.bottom
513 width: parent.width
514 dragAreaWidth: shell.edgeSize
515 available: tutorial.launcherEnabled
516 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
517 && !greeter.hasLockedApp
518 inverted: usageModeSettings.usageMode === "Staged"
519 shadeBackground: !tutorial.running
521 onShowDashHome: showHome()
522 onDash: showDash()
523 onDashSwipeChanged: {
524 if (dashSwipe) {
525 dash.setCurrentScope(0, false, true)
526 }
527 }
528 onLauncherApplicationSelected: {
529 if (!tutorial.running) {
530 greeter.notifyAboutToFocusApp(appId);
531 shell.activateApplication(appId)
532 }
533 }
534 onShownChanged: {
535 if (shown) {
536 panel.indicators.hide()
537 }
538 }
539 }
541 Wizard {
542 id: wizard
543 objectName: "wizard"
544 anchors.fill: parent
545 background: shell.background
547 function unlockWhenDoneWithWizard() {
548 if (!active) {
549 Connectivity.unlockAllModems();
550 }
551 }
553 Component.onCompleted: unlockWhenDoneWithWizard()
554 onActiveChanged: unlockWhenDoneWithWizard()
555 }
557 Rectangle {
558 id: modalNotificationBackground
560 visible: notifications.useModal && (notifications.state == "narrow")
561 color: "#000000"
562 anchors.fill: parent
563 opacity: 0.9
565 MouseArea {
566 anchors.fill: parent
567 }
568 }
570 Notifications {
571 id: notifications
573 model: NotificationBackend.Model
574 margin: units.gu(1)
576 y: topmostIsFullscreen ? 0 : panel.panelHeight
577 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
579 states: [
580 State {
581 name: "narrow"
582 when: overlay.width <= units.gu(60)
583 AnchorChanges {
584 target: notifications
585 anchors.left: parent.left
586 anchors.right: parent.right
587 }
588 },
589 State {
590 name: "wide"
591 when: overlay.width > units.gu(60)
592 AnchorChanges {
593 target: notifications
594 anchors.left: undefined
595 anchors.right: parent.right
596 }
597 PropertyChanges { target: notifications; width: units.gu(38) }
598 }
599 ]
600 }
601 }
603 Dialogs {
604 id: dialogs
605 anchors.fill: parent
606 z: overlay.z + 10
607 onPowerOffClicked: {
608 shutdownFadeOutRectangle.enabled = true;
609 shutdownFadeOutRectangle.visible = true;
610 shutdownFadeOut.start();
611 }
612 }
614 Connections {
615 target: SessionBroadcast
616 onShowHome: showHome()
617 }
619 Rectangle {
620 id: shutdownFadeOutRectangle
621 z: screenGrabber.z + 10
622 enabled: false
623 visible: false
624 color: "black"
625 anchors.fill: parent
626 opacity: 0.0
627 NumberAnimation on opacity {
628 id: shutdownFadeOut
629 from: 0.0
630 to: 1.0
631 onStopped: {
632 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
633 DBusUnitySessionService.Shutdown();
634 }
635 }
636 }
637 }