on e57efda help sign in
unity8/qml/Shell.qml
631 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 schema.id: "org.gnome.desktop.background"
111 }
113 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
114 Image {
115 id: gsImageTester
116 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
117 height: 0
118 width: 0
119 sourceSize.height: 0
120 sourceSize.width: 0
121 }
123 GSettings {
124 id: usageModeSettings
125 schema.id: "com.canonical.Unity8"
126 }
128 Binding {
129 target: LauncherModel
130 property: "applicationManager"
131 value: ApplicationManager
132 }
134 Component.onCompleted: {
135 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
136 if (ApplicationManager.count > 0) {
137 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
138 }
139 if (orientationLockEnabled) {
140 orientation = OrientationLock.savedOrientation;
141 }
142 }
144 VolumeControl {
145 id: volumeControl
146 }
148 DashCommunicator {
149 id: dash
150 objectName: "dashCommunicator"
151 }
153 PhysicalKeysMapper {
154 id: physicalKeysMapper
156 onPowerKeyLongPressed: dialogs.showPowerDialog()
157 onVolumeDownTriggered: volumeControl.volumeDown();
158 onVolumeUpTriggered: volumeControl.volumeUp();
159 onScreenshotTriggered: screenGrabber.capture();
160 onAltTabNext: applicationsDisplayLoader.item.altTabNext()
161 onAltTabPrevious: applicationsDisplayLoader.item.altTabPrevious();
162 }
164 ScreenGrabber {
165 id: screenGrabber
166 z: dialogs.z + 10
167 enabled: Powerd.status === Powerd.On
168 }
170 Binding {
171 target: ApplicationManager
172 property: "forceDashActive"
173 value: launcher.shown || launcher.dashSwipe
174 }
176 WindowKeysFilter {
177 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
178 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
179 }
181 Item {
182 id: stages
183 objectName: "stages"
184 width: parent.width
185 height: parent.height
186 visible: !ApplicationManager.empty
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.
233 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
234 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
235 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
237 property bool interactive: tutorial.spreadEnabled
238 && !greeter.shown
239 && panel.indicators.fullyClosed
240 && launcher.progress == 0
241 && !notifications.useModal
243 onInteractiveChanged: { if (interactive) { focus = true; } }
245 Binding {
246 target: applicationsDisplayLoader.item
247 property: "objectName"
248 value: "stage"
249 }
250 Binding {
251 target: applicationsDisplayLoader.item
252 property: "dragAreaWidth"
253 value: shell.edgeSize
254 }
255 Binding {
256 target: applicationsDisplayLoader.item
257 property: "maximizedAppTopMargin"
258 // Not just using panel.panelHeight as that changes depending on the focused app.
259 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
260 }
261 Binding {
262 target: applicationsDisplayLoader.item
263 property: "interactive"
264 value: applicationsDisplayLoader.interactive
265 }
266 Binding {
267 target: applicationsDisplayLoader.item
268 property: "spreadEnabled"
269 value: tutorial.spreadEnabled && !greeter.hasLockedApp
270 }
271 Binding {
272 target: applicationsDisplayLoader.item
273 property: "inverseProgress"
274 value: greeter.locked ? 0 : launcher.progress
275 }
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "orientation"
279 value: shell.orientation
280 }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "background"
284 value: shell.background
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "altTabPressed"
289 value: physicalKeysMapper.altTabPressed
290 }
291 }
292 }
294 InputMethod {
295 id: inputMethod
296 objectName: "inputMethod"
297 anchors { fill: parent; topMargin: panel.panelHeight }
298 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
299 }
301 Connections {
302 target: SurfaceManager
303 onSurfaceCreated: {
304 if (surface.type == MirSurfaceItem.InputMethod) {
305 inputMethod.surface = surface;
306 }
307 }
309 onSurfaceDestroyed: {
310 if (inputMethod.surface == surface) {
311 inputMethod.surface = null;
312 surface.parent = null;
313 }
314 if (!surface.parent) {
315 // there's no one displaying it. delete it right away
316 surface.release();
317 }
318 }
319 }
320 Connections {
321 target: SessionManager
322 onSessionStopping: {
323 if (!session.parentSession && !session.application) {
324 // nothing is using it. delete it right away
325 session.release();
326 }
327 }
328 }
330 Greeter {
331 id: greeter
332 objectName: "greeter"
334 hides: [launcher, panel.indicators]
335 tabletMode: shell.sideStageEnabled
336 launcherOffset: launcher.progress
337 forcedUnlock: tutorial.running
338 background: shell.background
340 anchors.fill: parent
341 anchors.topMargin: panel.panelHeight
343 // avoid overlapping with Launcher's edge drag area
344 // FIXME: Fix TouchRegistry & friends and remove this workaround
345 // Issue involves launcher's DDA getting disabled on a long
346 // left-edge drag
347 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
349 onSessionStarted: {
350 launcher.hide();
351 }
353 onTease: {
354 if (!tutorial.running) {
355 launcher.tease();
356 }
357 }
359 onEmergencyCall: startLockedApp("dialer-app")
361 Timer {
362 // See powerConnection for why this is useful
363 id: showGreeterDelayed
364 interval: 1
365 onTriggered: {
366 greeter.forceShow();
367 }
368 }
370 Binding {
371 target: ApplicationManager
372 property: "suspended"
373 value: greeter.shown
374 }
375 }
377 Connections {
378 id: callConnection
379 target: callManager
381 onHasCallsChanged: {
382 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
383 // We just received an incoming call while locked. The
384 // indicator will have already launched dialer-app for us, but
385 // there is a race between "hasCalls" changing and the dialer
386 // starting up. So in case we lose that race, we'll start/
387 // focus the dialer ourselves here too. Even if the indicator
388 // didn't launch the dialer for some reason (or maybe a call
389 // started via some other means), if an active call is
390 // happening, we want to be in the dialer.
391 startLockedApp("dialer-app")
392 }
393 }
394 }
396 Connections {
397 id: powerConnection
398 target: Powerd
400 onStatusChanged: {
401 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
402 !callManager.hasCalls && !tutorial.running) {
403 // We don't want to simply call greeter.showNow() here, because
404 // that will take too long. Qt will delay button event
405 // handling until the greeter is done loading and may think the
406 // user held down the power button the whole time, leading to a
407 // power dialog being shown. Instead, delay showing the
408 // greeter until we've finished handling the event. We could
409 // make the greeter load asynchronously instead, but that
410 // introduces a whole host of timing issues, especially with
411 // its animations. So this is simpler.
412 showGreeterDelayed.start();
413 }
414 }
415 }
417 function showHome() {
418 if (tutorial.running) {
419 return
420 }
422 greeter.notifyAboutToFocusApp("unity8-dash");
424 var animate = !LightDM.Greeter.active && !stages.shown
425 dash.setCurrentScope(0, animate, false)
426 ApplicationManager.requestFocusApplication("unity8-dash")
427 }
429 function showDash() {
430 if (greeter.notifyShowingDashFromDrag()) {
431 launcher.fadeOut();
432 }
434 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
435 ApplicationManager.requestFocusApplication("unity8-dash")
436 launcher.fadeOut();
437 }
438 }
440 Item {
441 id: overlay
442 z: 10
444 anchors.fill: parent
446 Panel {
447 id: panel
448 objectName: "panel"
449 anchors.fill: parent //because this draws indicator menus
450 indicators {
451 hides: [launcher]
452 available: tutorial.panelEnabled
453 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
454 && !greeter.hasLockedApp
455 contentEnabled: tutorial.panelContentEnabled
456 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
458 minimizedPanelHeight: units.gu(3)
459 expandedPanelHeight: units.gu(7)
461 indicatorsModel: Indicators.IndicatorsModel {
462 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
463 profile: indicatorProfile
464 Component.onCompleted: load()
465 }
466 }
467 callHint {
468 greeterShown: greeter.shown
469 }
471 property bool topmostApplicationIsFullscreen:
472 ApplicationManager.focusedApplicationId &&
473 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
475 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
476 || greeter.hasLockedApp
477 }
479 Launcher {
480 id: launcher
481 objectName: "launcher"
483 readonly property bool dashSwipe: progress > 0
485 anchors.top: parent.top
486 anchors.topMargin: inverted ? 0 : panel.panelHeight
487 anchors.bottom: parent.bottom
488 width: parent.width
489 dragAreaWidth: shell.edgeSize
490 available: tutorial.launcherEnabled
491 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
492 && !greeter.hasLockedApp
493 inverted: usageModeSettings.usageMode === "Staged"
494 shadeBackground: !tutorial.running
496 onShowDashHome: showHome()
497 onDash: showDash()
498 onDashSwipeChanged: {
499 if (dashSwipe) {
500 dash.setCurrentScope(0, false, true)
501 }
502 }
503 onLauncherApplicationSelected: {
504 if (!tutorial.running) {
505 greeter.notifyAboutToFocusApp(appId);
506 shell.activateApplication(appId)
507 }
508 }
509 onShownChanged: {
510 if (shown) {
511 panel.indicators.hide()
512 }
513 }
514 }
516 Wizard {
517 id: wizard
518 objectName: "wizard"
519 anchors.fill: parent
520 background: shell.background
522 function unlockWhenDoneWithWizard() {
523 if (!active) {
524 Connectivity.unlockAllModems();
525 }
526 }
528 Component.onCompleted: unlockWhenDoneWithWizard()
529 onActiveChanged: unlockWhenDoneWithWizard()
530 }
532 Rectangle {
533 id: modalNotificationBackground
535 visible: notifications.useModal && (notifications.state == "narrow")
536 color: "#000000"
537 anchors.fill: parent
538 opacity: 0.9
540 MouseArea {
541 anchors.fill: parent
542 }
543 }
545 Notifications {
546 id: notifications
548 model: NotificationBackend.Model
549 margin: units.gu(1)
551 y: topmostIsFullscreen ? 0 : panel.panelHeight
552 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
554 states: [
555 State {
556 name: "narrow"
557 when: overlay.width <= units.gu(60)
558 AnchorChanges {
559 target: notifications
560 anchors.left: parent.left
561 anchors.right: parent.right
562 }
563 },
564 State {
565 name: "wide"
566 when: overlay.width > units.gu(60)
567 AnchorChanges {
568 target: notifications
569 anchors.left: undefined
570 anchors.right: parent.right
571 }
572 PropertyChanges { target: notifications; width: units.gu(38) }
573 }
574 ]
575 }
576 }
578 Dialogs {
579 id: dialogs
580 anchors.fill: parent
581 z: overlay.z + 10
582 onPowerOffClicked: {
583 shutdownFadeOutRectangle.enabled = true;
584 shutdownFadeOutRectangle.visible = true;
585 shutdownFadeOut.start();
586 }
587 }
589 Tutorial {
590 id: tutorial
591 objectName: "tutorial"
592 active: AccountsService.demoEdges
593 paused: LightDM.Greeter.active
594 launcher: launcher
595 panel: panel
596 stages: stages
597 overlay: overlay
598 edgeSize: shell.edgeSize
600 onFinished: {
601 AccountsService.demoEdges = false;
602 active = false; // for immediate response / if AS is having problems
603 }
604 }
606 Connections {
607 target: SessionBroadcast
608 onShowHome: showHome()
609 }
611 Rectangle {
612 id: shutdownFadeOutRectangle
613 z: screenGrabber.z + 10
614 enabled: false
615 visible: false
616 color: "black"
617 anchors.fill: parent
618 opacity: 0.0
619 NumberAnimation on opacity {
620 id: shutdownFadeOut
621 from: 0.0
622 to: 1.0
623 onStopped: {
624 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
625 DBusUnitySessionService.Shutdown();
626 }
627 }
628 }
629 }