on 8da608c help sign in
unity8/qml/Shell.qml
625 lines 21 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 function activateApplication(appId) {
83 if (ApplicationManager.findApplication(appId)) {
84 ApplicationManager.requestFocusApplication(appId);
85 } else {
86 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
87 ApplicationManager.startApplication(appId, execFlags);
88 }
89 }
91 function startLockedApp(app) {
92 if (greeter.locked) {
93 greeter.lockedApp = app;
94 }
95 shell.activateApplication(app);
96 }
98 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
99 Image {
100 id: asImageTester
101 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
102 height: 0
103 width: 0
104 sourceSize.height: 0
105 sourceSize.width: 0
106 }
108 GSettings {
109 id: backgroundSettings
110 objectName: "backgroundSettings"
111 schema.id: "org.gnome.desktop.background"
112 }
114 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
115 Image {
116 id: gsImageTester
117 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
118 height: 0
119 width: 0
120 sourceSize.height: 0
121 sourceSize.width: 0
122 }
124 GSettings {
125 id: usageModeSettings
126 schema.id: "com.canonical.Unity8"
127 }
129 Binding {
130 target: LauncherModel
131 property: "applicationManager"
132 value: ApplicationManager
133 }
135 Component.onCompleted: {
136 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
137 if (ApplicationManager.count > 0) {
138 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
139 }
140 if (orientationLockEnabled) {
141 orientation = OrientationLock.savedOrientation;
142 }
143 }
145 VolumeControl {
146 id: volumeControl
147 }
149 DashCommunicator {
150 id: dash
151 objectName: "dashCommunicator"
152 }
154 PhysicalKeysMapper {
155 id: physicalKeysMapper
157 onPowerKeyLongPressed: dialogs.showPowerDialog()
158 onVolumeDownTriggered: volumeControl.volumeDown();
159 onVolumeUpTriggered: volumeControl.volumeUp();
160 onScreenshotTriggered: screenGrabber.capture();
161 }
163 ScreenGrabber {
164 id: screenGrabber
165 z: dialogs.z + 10
166 enabled: Powerd.status === Powerd.On
167 }
169 Binding {
170 target: ApplicationManager
171 property: "forceDashActive"
172 value: launcher.shown || launcher.dashSwipe
173 }
175 WindowKeysFilter {
176 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
177 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
178 }
180 Item {
181 id: stages
182 objectName: "stages"
183 width: parent.width
184 height: parent.height
185 visible: !ApplicationManager.empty
187 Connections {
188 target: ApplicationManager
190 // This signal is also fired when we try to focus the current app
191 // again. We rely on this!
192 onFocusedApplicationIdChanged: {
193 var appId = ApplicationManager.focusedApplicationId;
195 if (tutorial.running && appId != "" && appId != "unity8-dash") {
196 // If this happens on first boot, we may be in edge
197 // tutorial or wizard while receiving a call. But a call
198 // is more important than wizard so just bail out of those.
199 tutorial.finish();
200 wizard.hide();
201 }
203 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
204 // If we are in the middle of a call, make dialer lockedApp and show it.
205 // This can happen if user backs out of dialer back to greeter, then
206 // launches dialer again.
207 greeter.lockedApp = appId;
208 }
209 greeter.notifyAppFocused(appId);
211 panel.indicators.hide();
212 }
214 onApplicationAdded: {
215 launcher.hide();
216 }
217 }
219 Loader {
220 id: applicationsDisplayLoader
221 objectName: "applicationsDisplayLoader"
222 anchors.fill: parent
224 // When we have a locked app, we only want to show that one app.
225 // FIXME: do this in a less traumatic way. We currently only allow
226 // locked apps in phone mode (see FIXME in Lockscreen component in
227 // this same file). When that changes, we need to do something
228 // nicer here. But this code is currently just to prevent a
229 // theoretical attack where user enters lockedApp mode, then makes
230 // the screen larger (maybe connects to monitor) and tries to enter
231 // tablet mode.
232 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
233 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
234 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
236 property bool interactive: tutorial.spreadEnabled
237 && !greeter.shown
238 && panel.indicators.fullyClosed
239 && launcher.progress == 0
240 && !notifications.useModal
242 onInteractiveChanged: { if (interactive) { focus = true; } }
244 Binding {
245 target: applicationsDisplayLoader.item
246 property: "objectName"
247 value: "stage"
248 }
249 Binding {
250 target: applicationsDisplayLoader.item
251 property: "dragAreaWidth"
252 value: shell.edgeSize
253 }
254 Binding {
255 target: applicationsDisplayLoader.item
256 property: "maximizedAppTopMargin"
257 // Not just using panel.panelHeight as that changes depending on the focused app.
258 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
259 }
260 Binding {
261 target: applicationsDisplayLoader.item
262 property: "interactive"
263 value: applicationsDisplayLoader.interactive
264 }
265 Binding {
266 target: applicationsDisplayLoader.item
267 property: "spreadEnabled"
268 value: tutorial.spreadEnabled && !greeter.hasLockedApp
269 }
270 Binding {
271 target: applicationsDisplayLoader.item
272 property: "inverseProgress"
273 value: greeter.locked ? 0 : launcher.progress
274 }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "orientation"
278 value: shell.orientation
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "background"
283 value: shell.background
284 }
285 }
286 }
288 InputMethod {
289 id: inputMethod
290 objectName: "inputMethod"
291 anchors { fill: parent; topMargin: panel.panelHeight }
292 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
293 }
295 Connections {
296 target: SurfaceManager
297 onSurfaceCreated: {
298 if (surface.type == MirSurfaceItem.InputMethod) {
299 inputMethod.surface = surface;
300 }
301 }
303 onSurfaceDestroyed: {
304 if (inputMethod.surface == surface) {
305 inputMethod.surface = null;
306 surface.parent = null;
307 }
308 if (!surface.parent) {
309 // there's no one displaying it. delete it right away
310 surface.release();
311 }
312 }
313 }
314 Connections {
315 target: SessionManager
316 onSessionStopping: {
317 if (!session.parentSession && !session.application) {
318 // nothing is using it. delete it right away
319 session.release();
320 }
321 }
322 }
324 Greeter {
325 id: greeter
326 objectName: "greeter"
328 hides: [launcher, panel.indicators]
329 tabletMode: shell.sideStageEnabled
330 launcherOffset: launcher.progress
331 forcedUnlock: tutorial.running
332 background: shell.background
334 anchors.fill: parent
335 anchors.topMargin: panel.panelHeight
337 // avoid overlapping with Launcher's edge drag area
338 // FIXME: Fix TouchRegistry & friends and remove this workaround
339 // Issue involves launcher's DDA getting disabled on a long
340 // left-edge drag
341 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
343 onSessionStarted: {
344 launcher.hide();
345 }
347 onTease: {
348 if (!tutorial.running) {
349 launcher.tease();
350 }
351 }
353 onEmergencyCall: startLockedApp("dialer-app")
355 Timer {
356 // See powerConnection for why this is useful
357 id: showGreeterDelayed
358 interval: 1
359 onTriggered: {
360 greeter.forceShow();
361 }
362 }
364 Binding {
365 target: ApplicationManager
366 property: "suspended"
367 value: greeter.shown
368 }
369 }
371 Connections {
372 id: callConnection
373 target: callManager
375 onHasCallsChanged: {
376 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
377 // We just received an incoming call while locked. The
378 // indicator will have already launched dialer-app for us, but
379 // there is a race between "hasCalls" changing and the dialer
380 // starting up. So in case we lose that race, we'll start/
381 // focus the dialer ourselves here too. Even if the indicator
382 // didn't launch the dialer for some reason (or maybe a call
383 // started via some other means), if an active call is
384 // happening, we want to be in the dialer.
385 startLockedApp("dialer-app")
386 }
387 }
388 }
390 Connections {
391 id: powerConnection
392 target: Powerd
394 onStatusChanged: {
395 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
396 !callManager.hasCalls && !tutorial.running) {
397 // We don't want to simply call greeter.showNow() here, because
398 // that will take too long. Qt will delay button event
399 // handling until the greeter is done loading and may think the
400 // user held down the power button the whole time, leading to a
401 // power dialog being shown. Instead, delay showing the
402 // greeter until we've finished handling the event. We could
403 // make the greeter load asynchronously instead, but that
404 // introduces a whole host of timing issues, especially with
405 // its animations. So this is simpler.
406 showGreeterDelayed.start();
407 }
408 }
409 }
411 function showHome() {
412 if (tutorial.running) {
413 return
414 }
416 greeter.notifyAboutToFocusApp("unity8-dash");
418 var animate = !LightDM.Greeter.active && !stages.shown
419 dash.setCurrentScope(0, animate, false)
420 ApplicationManager.requestFocusApplication("unity8-dash")
421 }
423 function showDash() {
424 if (greeter.notifyShowingDashFromDrag()) {
425 launcher.fadeOut();
426 }
428 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
429 ApplicationManager.requestFocusApplication("unity8-dash")
430 launcher.fadeOut();
431 }
432 }
434 Item {
435 id: overlay
436 z: 10
438 anchors.fill: parent
440 Panel {
441 id: panel
442 objectName: "panel"
443 anchors.fill: parent //because this draws indicator menus
444 indicators {
445 hides: [launcher]
446 available: tutorial.panelEnabled
447 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
448 && !greeter.hasLockedApp
449 contentEnabled: tutorial.panelContentEnabled
450 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
452 minimizedPanelHeight: units.gu(3)
453 expandedPanelHeight: units.gu(7)
455 indicatorsModel: Indicators.IndicatorsModel {
456 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
457 profile: indicatorProfile
458 Component.onCompleted: load()
459 }
460 }
461 callHint {
462 greeterShown: greeter.shown
463 }
465 property bool topmostApplicationIsFullscreen:
466 ApplicationManager.focusedApplicationId &&
467 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
469 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
470 || greeter.hasLockedApp
471 }
473 Launcher {
474 id: launcher
475 objectName: "launcher"
477 readonly property bool dashSwipe: progress > 0
479 anchors.top: parent.top
480 anchors.topMargin: inverted ? 0 : panel.panelHeight
481 anchors.bottom: parent.bottom
482 width: parent.width
483 dragAreaWidth: shell.edgeSize
484 available: tutorial.launcherEnabled
485 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
486 && !greeter.hasLockedApp
487 inverted: usageModeSettings.usageMode === "Staged"
488 shadeBackground: !tutorial.running
490 onShowDashHome: showHome()
491 onDash: showDash()
492 onDashSwipeChanged: {
493 if (dashSwipe) {
494 dash.setCurrentScope(0, false, true)
495 }
496 }
497 onLauncherApplicationSelected: {
498 if (!tutorial.running) {
499 greeter.notifyAboutToFocusApp(appId);
500 shell.activateApplication(appId)
501 }
502 }
503 onShownChanged: {
504 if (shown) {
505 panel.indicators.hide()
506 }
507 }
508 }
510 Wizard {
511 id: wizard
512 objectName: "wizard"
513 anchors.fill: parent
514 background: shell.background
516 function unlockWhenDoneWithWizard() {
517 if (!active) {
518 Connectivity.unlockAllModems();
519 }
520 }
522 Component.onCompleted: unlockWhenDoneWithWizard()
523 onActiveChanged: unlockWhenDoneWithWizard()
524 }
526 Rectangle {
527 id: modalNotificationBackground
529 visible: notifications.useModal && (notifications.state == "narrow")
530 color: "#000000"
531 anchors.fill: parent
532 opacity: 0.9
534 MouseArea {
535 anchors.fill: parent
536 }
537 }
539 Notifications {
540 id: notifications
542 model: NotificationBackend.Model
543 margin: units.gu(1)
545 y: topmostIsFullscreen ? 0 : panel.panelHeight
546 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
548 states: [
549 State {
550 name: "narrow"
551 when: overlay.width <= units.gu(60)
552 AnchorChanges {
553 target: notifications
554 anchors.left: parent.left
555 anchors.right: parent.right
556 }
557 },
558 State {
559 name: "wide"
560 when: overlay.width > units.gu(60)
561 AnchorChanges {
562 target: notifications
563 anchors.left: undefined
564 anchors.right: parent.right
565 }
566 PropertyChanges { target: notifications; width: units.gu(38) }
567 }
568 ]
569 }
570 }
572 Dialogs {
573 id: dialogs
574 anchors.fill: parent
575 z: overlay.z + 10
576 onPowerOffClicked: {
577 shutdownFadeOutRectangle.enabled = true;
578 shutdownFadeOutRectangle.visible = true;
579 shutdownFadeOut.start();
580 }
581 }
583 Tutorial {
584 id: tutorial
585 objectName: "tutorial"
586 active: AccountsService.demoEdges
587 paused: LightDM.Greeter.active
588 launcher: launcher
589 panel: panel
590 stages: stages
591 overlay: overlay
592 edgeSize: shell.edgeSize
594 onFinished: {
595 AccountsService.demoEdges = false;
596 active = false; // for immediate response / if AS is having problems
597 }
598 }
600 Connections {
601 target: SessionBroadcast
602 onShowHome: showHome()
603 }
605 Rectangle {
606 id: shutdownFadeOutRectangle
607 z: screenGrabber.z + 10
608 enabled: false
609 visible: false
610 color: "black"
611 anchors.fill: parent
612 opacity: 0.0
613 NumberAnimation on opacity {
614 id: shutdownFadeOut
615 from: 0.0
616 to: 1.0
617 onStopped: {
618 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
619 DBusUnitySessionService.Shutdown();
620 }
621 }
622 }
623 }