on daa5708 help sign in
unity8/qml/Shell.qml
614 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.Launcher 0.1
27import Utils 0.1
28import LightDM 0.1 as LightDM
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
44Item {
45 id: shell
47 // Disable everything while greeter is waiting, so that the user can't swipe
48 // the greeter or launcher until we know whether the session is locked.
49 enabled: !greeter.waiting
51 // this is only here to select the width / height of the window if not running fullscreen
52 property bool tablet: false
53 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
54 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
56 property real edgeSize: units.gu(2)
57 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
58 property url background: asImageTester.status == Image.Ready ? asImageTester.source
59 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
60 readonly property real panelHeight: panel.panelHeight
62 property bool sideStageEnabled: shell.width >= units.gu(100)
63 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
65 property int orientation
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67 onDeviceOrientationAngleChanged: {
68 if (!OrientationLock.enabled) {
69 orientation = Screen.orientation;
70 }
71 }
72 readonly property bool orientationLockEnabled: OrientationLock.enabled
73 onOrientationLockEnabledChanged: {
74 if (orientationLockEnabled) {
75 OrientationLock.savedOrientation = Screen.orientation;
76 } else {
77 orientation = Screen.orientation;
78 }
79 }
81 function activateApplication(appId) {
82 if (ApplicationManager.findApplication(appId)) {
83 ApplicationManager.requestFocusApplication(appId);
84 } else {
85 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
86 ApplicationManager.startApplication(appId, execFlags);
87 }
88 }
90 function startLockedApp(app) {
91 if (greeter.locked) {
92 greeter.lockedApp = app;
93 }
94 shell.activateApplication(app);
95 }
97 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
98 Image {
99 id: asImageTester
100 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
101 height: 0
102 width: 0
103 sourceSize.height: 0
104 sourceSize.width: 0
105 }
107 GSettings {
108 id: backgroundSettings
109 objectName: "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 }
162 ScreenGrabber {
163 id: screenGrabber
164 z: dialogs.z + 10
165 enabled: Powerd.status === Powerd.On
166 }
168 Binding {
169 target: ApplicationManager
170 property: "forceDashActive"
171 value: launcher.shown || launcher.dashSwipe
172 }
174 WindowKeysFilter {
175 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
176 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
177 }
179 Item {
180 id: stages
181 objectName: "stages"
182 width: parent.width
183 height: parent.height
184 visible: !ApplicationManager.empty
186 Connections {
187 target: ApplicationManager
189 // This signal is also fired when we try to focus the current app
190 // again. We rely on this!
191 onFocusedApplicationIdChanged: {
192 var appId = ApplicationManager.focusedApplicationId;
194 if (tutorial.running && appId != "unity8-dash") {
195 // If this happens on first boot, we may be in edge
196 // tutorial or wizard while receiving a call. But a call
197 // is more important than wizard so just bail out of those.
198 tutorial.finish();
199 wizard.hide();
200 }
202 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
203 // If we are in the middle of a call, make dialer lockedApp and show it.
204 // This can happen if user backs out of dialer back to greeter, then
205 // launches dialer again.
206 greeter.lockedApp = appId;
207 }
208 greeter.notifyAppFocused(appId);
210 panel.indicators.hide();
211 }
213 onApplicationAdded: {
214 launcher.hide();
215 }
216 }
218 Loader {
219 id: applicationsDisplayLoader
220 objectName: "applicationsDisplayLoader"
221 anchors.fill: parent
223 // When we have a locked app, we only want to show that one app.
224 // FIXME: do this in a less traumatic way. We currently only allow
225 // locked apps in phone mode (see FIXME in Lockscreen component in
226 // this same file). When that changes, we need to do something
227 // nicer here. But this code is currently just to prevent a
228 // theoretical attack where user enters lockedApp mode, then makes
229 // the screen larger (maybe connects to monitor) and tries to enter
230 // tablet mode.
231 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
232 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
233 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
235 property bool interactive: tutorial.spreadEnabled
236 && !greeter.shown
237 && panel.indicators.fullyClosed
238 && launcher.progress == 0
239 && !notifications.useModal
241 onInteractiveChanged: { if (interactive) { focus = true; } }
243 Binding {
244 target: applicationsDisplayLoader.item
245 property: "objectName"
246 value: "stage"
247 }
248 Binding {
249 target: applicationsDisplayLoader.item
250 property: "dragAreaWidth"
251 value: shell.edgeSize
252 }
253 Binding {
254 target: applicationsDisplayLoader.item
255 property: "maximizedAppTopMargin"
256 // Not just using panel.panelHeight as that changes depending on the focused app.
257 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
258 }
259 Binding {
260 target: applicationsDisplayLoader.item
261 property: "interactive"
262 value: applicationsDisplayLoader.interactive
263 }
264 Binding {
265 target: applicationsDisplayLoader.item
266 property: "spreadEnabled"
267 value: tutorial.spreadEnabled && !greeter.hasLockedApp
268 }
269 Binding {
270 target: applicationsDisplayLoader.item
271 property: "inverseProgress"
272 value: greeter.locked ? 0 : launcher.progress
273 }
274 Binding {
275 target: applicationsDisplayLoader.item
276 property: "orientation"
277 value: shell.orientation
278 }
279 Binding {
280 target: applicationsDisplayLoader.item
281 property: "background"
282 value: shell.background
283 }
284 }
285 }
287 InputMethod {
288 id: inputMethod
289 objectName: "inputMethod"
290 anchors { fill: parent; topMargin: panel.panelHeight }
291 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
292 }
294 Connections {
295 target: SurfaceManager
296 onSurfaceCreated: {
297 if (surface.type == MirSurfaceItem.InputMethod) {
298 inputMethod.surface = surface;
299 }
300 }
302 onSurfaceDestroyed: {
303 if (inputMethod.surface == surface) {
304 inputMethod.surface = null;
305 surface.parent = null;
306 }
307 if (!surface.parent) {
308 // there's no one displaying it. delete it right away
309 surface.release();
310 }
311 }
312 }
313 Connections {
314 target: SessionManager
315 onSessionStopping: {
316 if (!session.parentSession && !session.application) {
317 // nothing is using it. delete it right away
318 session.release();
319 }
320 }
321 }
323 Greeter {
324 id: greeter
325 objectName: "greeter"
327 hides: [launcher, panel.indicators]
328 tabletMode: shell.sideStageEnabled
329 launcherOffset: launcher.progress
330 forcedUnlock: tutorial.running
331 background: shell.background
333 anchors.fill: parent
334 anchors.topMargin: panel.panelHeight
336 // avoid overlapping with Launcher's edge drag area
337 // FIXME: Fix TouchRegistry & friends and remove this workaround
338 // Issue involves launcher's DDA getting disabled on a long
339 // left-edge drag
340 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
342 onSessionStarted: {
343 launcher.hide();
344 }
346 onTease: {
347 if (!tutorial.running) {
348 launcher.tease();
349 }
350 }
352 onEmergencyCall: startLockedApp("dialer-app")
354 Timer {
355 // See powerConnection for why this is useful
356 id: showGreeterDelayed
357 interval: 1
358 onTriggered: {
359 greeter.forceShow();
360 }
361 }
363 Binding {
364 target: ApplicationManager
365 property: "suspended"
366 value: greeter.shown
367 }
368 }
370 Connections {
371 id: callConnection
372 target: callManager
374 onHasCallsChanged: {
375 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
376 // We just received an incoming call while locked. The
377 // indicator will have already launched dialer-app for us, but
378 // there is a race between "hasCalls" changing and the dialer
379 // starting up. So in case we lose that race, we'll start/
380 // focus the dialer ourselves here too. Even if the indicator
381 // didn't launch the dialer for some reason (or maybe a call
382 // started via some other means), if an active call is
383 // happening, we want to be in the dialer.
384 startLockedApp("dialer-app")
385 }
386 }
387 }
389 Connections {
390 id: powerConnection
391 target: Powerd
393 onStatusChanged: {
394 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
395 !callManager.hasCalls && !tutorial.running) {
396 // We don't want to simply call greeter.showNow() here, because
397 // that will take too long. Qt will delay button event
398 // handling until the greeter is done loading and may think the
399 // user held down the power button the whole time, leading to a
400 // power dialog being shown. Instead, delay showing the
401 // greeter until we've finished handling the event. We could
402 // make the greeter load asynchronously instead, but that
403 // introduces a whole host of timing issues, especially with
404 // its animations. So this is simpler.
405 showGreeterDelayed.start();
406 }
407 }
408 }
410 function showHome() {
411 if (tutorial.running) {
412 return
413 }
415 greeter.notifyAboutToFocusApp("unity8-dash");
417 var animate = !LightDM.Greeter.active && !stages.shown
418 dash.setCurrentScope(0, animate, false)
419 ApplicationManager.requestFocusApplication("unity8-dash")
420 }
422 function showDash() {
423 if (greeter.notifyShowingDashFromDrag()) {
424 launcher.fadeOut();
425 }
427 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
428 ApplicationManager.requestFocusApplication("unity8-dash")
429 launcher.fadeOut();
430 }
431 }
433 Item {
434 id: overlay
435 z: 10
437 anchors.fill: parent
439 Panel {
440 id: panel
441 objectName: "panel"
442 anchors.fill: parent //because this draws indicator menus
443 indicators {
444 hides: [launcher]
445 available: tutorial.panelEnabled
446 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
447 && !greeter.hasLockedApp
448 contentEnabled: tutorial.panelContentEnabled
449 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
451 minimizedPanelHeight: units.gu(3)
452 expandedPanelHeight: units.gu(7)
454 indicatorsModel: Indicators.IndicatorsModel {
455 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
456 profile: indicatorProfile
457 Component.onCompleted: load()
458 }
459 }
460 callHint {
461 greeterShown: greeter.shown
462 }
464 property bool topmostApplicationIsFullscreen:
465 ApplicationManager.focusedApplicationId &&
466 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
468 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
469 || greeter.hasLockedApp
470 }
472 Launcher {
473 id: launcher
474 objectName: "launcher"
476 readonly property bool dashSwipe: progress > 0
478 anchors.top: parent.top
479 anchors.topMargin: inverted ? 0 : panel.panelHeight
480 anchors.bottom: parent.bottom
481 width: parent.width
482 dragAreaWidth: shell.edgeSize
483 available: tutorial.launcherEnabled
484 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
485 && !greeter.hasLockedApp
486 inverted: usageModeSettings.usageMode === "Staged"
487 shadeBackground: !tutorial.running
489 onShowDashHome: showHome()
490 onDash: showDash()
491 onDashSwipeChanged: {
492 if (dashSwipe) {
493 dash.setCurrentScope(0, false, true)
494 }
495 }
496 onLauncherApplicationSelected: {
497 if (!tutorial.running) {
498 greeter.notifyAboutToFocusApp(appId);
499 shell.activateApplication(appId)
500 }
501 }
502 onShownChanged: {
503 if (shown) {
504 panel.indicators.hide()
505 }
506 }
507 }
509 Wizard {
510 id: wizard
511 anchors.fill: parent
512 background: shell.background
513 }
515 Rectangle {
516 id: modalNotificationBackground
518 visible: notifications.useModal && (notifications.state == "narrow")
519 color: "#000000"
520 anchors.fill: parent
521 opacity: 0.9
523 MouseArea {
524 anchors.fill: parent
525 }
526 }
528 Notifications {
529 id: notifications
531 model: NotificationBackend.Model
532 margin: units.gu(1)
534 y: topmostIsFullscreen ? 0 : panel.panelHeight
535 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
537 states: [
538 State {
539 name: "narrow"
540 when: overlay.width <= units.gu(60)
541 AnchorChanges {
542 target: notifications
543 anchors.left: parent.left
544 anchors.right: parent.right
545 }
546 },
547 State {
548 name: "wide"
549 when: overlay.width > units.gu(60)
550 AnchorChanges {
551 target: notifications
552 anchors.left: undefined
553 anchors.right: parent.right
554 }
555 PropertyChanges { target: notifications; width: units.gu(38) }
556 }
557 ]
558 }
559 }
561 Dialogs {
562 id: dialogs
563 anchors.fill: parent
564 z: overlay.z + 10
565 onPowerOffClicked: {
566 shutdownFadeOutRectangle.enabled = true;
567 shutdownFadeOutRectangle.visible = true;
568 shutdownFadeOut.start();
569 }
570 }
572 Tutorial {
573 id: tutorial
574 objectName: "tutorial"
575 active: AccountsService.demoEdges
576 paused: LightDM.Greeter.active
577 launcher: launcher
578 panel: panel
579 stages: stages
580 overlay: overlay
581 edgeSize: shell.edgeSize
583 onFinished: {
584 AccountsService.demoEdges = false;
585 active = false; // for immediate response / if AS is having problems
586 }
587 }
589 Connections {
590 target: SessionBroadcast
591 onShowHome: showHome()
592 }
594 Rectangle {
595 id: shutdownFadeOutRectangle
596 z: screenGrabber.z + 10
597 enabled: false
598 visible: false
599 color: "black"
600 anchors.fill: parent
601 opacity: 0.0
602 NumberAnimation on opacity {
603 id: shutdownFadeOut
604 from: 0.0
605 to: 1.0
606 onStopped: {
607 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
608 DBusUnitySessionService.Shutdown();
609 }
610 }
611 }
612 }