on 1975165 help sign in
unity8/qml/Shell.qml
624 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 }
287 Tutorial {
288 id: tutorial
289 objectName: "tutorial"
290 anchors.fill: parent
291 active: AccountsService.demoEdges
292 paused: LightDM.Greeter.active
293 launcher: launcher
294 panel: panel
295 edgeSize: shell.edgeSize
297 onFinished: {
298 AccountsService.demoEdges = false;
299 active = false; // for immediate response / if AS is having problems
300 }
301 }
302 }
304 InputMethod {
305 id: inputMethod
306 objectName: "inputMethod"
307 anchors { fill: parent; topMargin: panel.panelHeight }
308 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
309 }
311 Connections {
312 target: SurfaceManager
313 onSurfaceCreated: {
314 if (surface.type == MirSurfaceItem.InputMethod) {
315 inputMethod.surface = surface;
316 }
317 }
319 onSurfaceDestroyed: {
320 if (inputMethod.surface == surface) {
321 inputMethod.surface = null;
322 surface.parent = null;
323 }
324 if (!surface.parent) {
325 // there's no one displaying it. delete it right away
326 surface.release();
327 }
328 }
329 }
330 Connections {
331 target: SessionManager
332 onSessionStopping: {
333 if (!session.parentSession && !session.application) {
334 // nothing is using it. delete it right away
335 session.release();
336 }
337 }
338 }
340 Greeter {
341 id: greeter
342 objectName: "greeter"
344 hides: [launcher, panel.indicators]
345 tabletMode: shell.sideStageEnabled
346 launcherOffset: launcher.progress
347 forcedUnlock: tutorial.running
348 background: shell.background
350 anchors.fill: parent
351 anchors.topMargin: panel.panelHeight
353 // avoid overlapping with Launcher's edge drag area
354 // FIXME: Fix TouchRegistry & friends and remove this workaround
355 // Issue involves launcher's DDA getting disabled on a long
356 // left-edge drag
357 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
359 onSessionStarted: {
360 launcher.hide();
361 }
363 onTease: {
364 if (!tutorial.running) {
365 launcher.tease();
366 }
367 }
369 onEmergencyCall: startLockedApp("dialer-app")
371 Timer {
372 // See powerConnection for why this is useful
373 id: showGreeterDelayed
374 interval: 1
375 onTriggered: {
376 greeter.forceShow();
377 }
378 }
380 Binding {
381 target: ApplicationManager
382 property: "suspended"
383 value: greeter.shown
384 }
385 }
387 Connections {
388 id: callConnection
389 target: callManager
391 onHasCallsChanged: {
392 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
393 // We just received an incoming call while locked. The
394 // indicator will have already launched dialer-app for us, but
395 // there is a race between "hasCalls" changing and the dialer
396 // starting up. So in case we lose that race, we'll start/
397 // focus the dialer ourselves here too. Even if the indicator
398 // didn't launch the dialer for some reason (or maybe a call
399 // started via some other means), if an active call is
400 // happening, we want to be in the dialer.
401 startLockedApp("dialer-app")
402 }
403 }
404 }
406 Connections {
407 id: powerConnection
408 target: Powerd
410 onStatusChanged: {
411 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
412 !callManager.hasCalls && !tutorial.running) {
413 // We don't want to simply call greeter.showNow() here, because
414 // that will take too long. Qt will delay button event
415 // handling until the greeter is done loading and may think the
416 // user held down the power button the whole time, leading to a
417 // power dialog being shown. Instead, delay showing the
418 // greeter until we've finished handling the event. We could
419 // make the greeter load asynchronously instead, but that
420 // introduces a whole host of timing issues, especially with
421 // its animations. So this is simpler.
422 showGreeterDelayed.start();
423 }
424 }
425 }
427 function showHome() {
428 if (tutorial.running) {
429 return
430 }
432 greeter.notifyAboutToFocusApp("unity8-dash");
434 var animate = !LightDM.Greeter.active && !stages.shown
435 dash.setCurrentScope(0, animate, false)
436 ApplicationManager.requestFocusApplication("unity8-dash")
437 }
439 function showDash() {
440 if (greeter.notifyShowingDashFromDrag()) {
441 launcher.fadeOut();
442 }
444 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
445 ApplicationManager.requestFocusApplication("unity8-dash")
446 launcher.fadeOut();
447 }
448 }
450 Item {
451 id: overlay
452 z: 10
454 anchors.fill: parent
456 Panel {
457 id: panel
458 objectName: "panel"
459 anchors.fill: parent //because this draws indicator menus
460 indicators {
461 hides: [launcher]
462 available: tutorial.panelEnabled
463 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
464 && !greeter.hasLockedApp
465 contentEnabled: tutorial.panelContentEnabled
466 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
468 minimizedPanelHeight: units.gu(3)
469 expandedPanelHeight: units.gu(7)
471 indicatorsModel: Indicators.IndicatorsModel {
472 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
473 profile: indicatorProfile
474 Component.onCompleted: load()
475 }
476 }
477 callHint {
478 greeterShown: greeter.shown
479 }
481 property bool topmostApplicationIsFullscreen:
482 ApplicationManager.focusedApplicationId &&
483 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
485 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
486 || greeter.hasLockedApp
487 }
489 Launcher {
490 id: launcher
491 objectName: "launcher"
493 readonly property bool dashSwipe: progress > 0
495 anchors.top: parent.top
496 anchors.topMargin: inverted ? 0 : panel.panelHeight
497 anchors.bottom: parent.bottom
498 width: parent.width
499 dragAreaWidth: shell.edgeSize
500 available: tutorial.launcherEnabled
501 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
502 && !greeter.hasLockedApp
503 inverted: usageModeSettings.usageMode === "Staged"
504 shadeBackground: !tutorial.running
506 onShowDashHome: showHome()
507 onDash: showDash()
508 onDashSwipeChanged: {
509 if (dashSwipe) {
510 dash.setCurrentScope(0, false, true)
511 }
512 }
513 onLauncherApplicationSelected: {
514 if (!tutorial.running) {
515 greeter.notifyAboutToFocusApp(appId);
516 shell.activateApplication(appId)
517 }
518 }
519 onShownChanged: {
520 if (shown) {
521 panel.indicators.hide()
522 }
523 }
524 }
526 Wizard {
527 id: wizard
528 objectName: "wizard"
529 anchors.fill: parent
530 background: shell.background
532 function unlockWhenDoneWithWizard() {
533 if (!active) {
534 Connectivity.unlockAllModems();
535 }
536 }
538 Component.onCompleted: unlockWhenDoneWithWizard()
539 onActiveChanged: unlockWhenDoneWithWizard()
540 }
542 Rectangle {
543 id: modalNotificationBackground
545 visible: notifications.useModal && (notifications.state == "narrow")
546 color: "#000000"
547 anchors.fill: parent
548 opacity: 0.9
550 MouseArea {
551 anchors.fill: parent
552 }
553 }
555 Notifications {
556 id: notifications
558 model: NotificationBackend.Model
559 margin: units.gu(1)
561 y: topmostIsFullscreen ? 0 : panel.panelHeight
562 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
564 states: [
565 State {
566 name: "narrow"
567 when: overlay.width <= units.gu(60)
568 AnchorChanges {
569 target: notifications
570 anchors.left: parent.left
571 anchors.right: parent.right
572 }
573 },
574 State {
575 name: "wide"
576 when: overlay.width > units.gu(60)
577 AnchorChanges {
578 target: notifications
579 anchors.left: undefined
580 anchors.right: parent.right
581 }
582 PropertyChanges { target: notifications; width: units.gu(38) }
583 }
584 ]
585 }
586 }
588 Dialogs {
589 id: dialogs
590 anchors.fill: parent
591 z: overlay.z + 10
592 onPowerOffClicked: {
593 shutdownFadeOutRectangle.enabled = true;
594 shutdownFadeOutRectangle.visible = true;
595 shutdownFadeOut.start();
596 }
597 }
599 Connections {
600 target: SessionBroadcast
601 onShowHome: showHome()
602 }
604 Rectangle {
605 id: shutdownFadeOutRectangle
606 z: screenGrabber.z + 10
607 enabled: false
608 visible: false
609 color: "black"
610 anchors.fill: parent
611 opacity: 0.0
612 NumberAnimation on opacity {
613 id: shutdownFadeOut
614 from: 0.0
615 to: 1.0
616 onStopped: {
617 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
618 DBusUnitySessionService.Shutdown();
619 }
620 }
621 }
622 }