on c88018a help sign in
unity8/qml/Shell.qml
611 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 "Panel/Indicators"
39import "Wizard"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
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 Connectivity.unlockAllModems();
143 }
145 VolumeControl {
146 id: volumeControl
147 }
149 DashCommunicator {
150 id: dash
151 objectName: "dashCommunicator"
152 }
154 ScreenGrabber {
155 id: screenGrabber
156 z: edgeDemo.z + 10
157 enabled: Powerd.status === Powerd.On
158 }
160 Binding {
161 target: ApplicationManager
162 property: "forceDashActive"
163 value: launcher.shown || launcher.dashSwipe
164 }
166 VolumeKeyFilter {
167 id: volumeKeyFilter
168 onVolumeDownPressed: volumeControl.volumeDown()
169 onVolumeUpPressed: volumeControl.volumeUp()
170 onBothVolumeKeysPressed: screenGrabber.capture()
171 }
173 WindowKeysFilter {
174 Keys.onPressed: {
175 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
176 dialogs.onPowerKeyPressed();
177 event.accepted = true;
178 } else {
179 volumeKeyFilter.onKeyPressed(event.key);
180 event.accepted = false;
181 }
182 }
184 Keys.onReleased: {
185 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
186 dialogs.onPowerKeyReleased();
187 event.accepted = true;
188 } else {
189 volumeKeyFilter.onKeyReleased(event.key);
190 event.accepted = false;
191 }
192 }
193 }
195 Item {
196 id: stages
197 objectName: "stages"
198 width: parent.width
199 height: parent.height
200 visible: !ApplicationManager.empty
202 Connections {
203 target: ApplicationManager
205 onFocusRequested: {
206 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
207 // If we are in the middle of a call, make dialer lockedApp and show it.
208 // This can happen if user backs out of dialer back to greeter, then
209 // launches dialer again.
210 greeter.lockedApp = appId;
211 }
213 greeter.notifyAppFocused(appId);
214 }
216 onFocusedApplicationIdChanged: {
217 greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
218 panel.indicators.hide();
219 }
221 onApplicationAdded: {
222 if (edgeDemo.running && appId != "unity8-dash") {
223 // If this happens on first boot, we may be in edge
224 // tutorial or wizard while receiving a call. But a call
225 // is more important than wizard so just bail out of those.
226 edgeDemo.hideEdgeDemos();
227 wizard.hide();
228 }
230 greeter.notifyAppFocused(appId);
231 launcher.hide();
232 }
233 }
235 Loader {
236 id: applicationsDisplayLoader
237 objectName: "applicationsDisplayLoader"
238 anchors.fill: parent
240 // When we have a locked app, we only want to show that one app.
241 // FIXME: do this in a less traumatic way. We currently only allow
242 // locked apps in phone mode (see FIXME in Lockscreen component in
243 // this same file). When that changes, we need to do something
244 // nicer here. But this code is currently just to prevent a
245 // theoretical attack where user enters lockedApp mode, then makes
246 // the screen larger (maybe connects to monitor) and tries to enter
247 // tablet mode.
248 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
249 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
250 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
252 Binding {
253 target: applicationsDisplayLoader.item
254 property: "objectName"
255 value: "stage"
256 }
257 Binding {
258 target: applicationsDisplayLoader.item
259 property: "dragAreaWidth"
260 value: shell.edgeSize
261 }
262 Binding {
263 target: applicationsDisplayLoader.item
264 property: "maximizedAppTopMargin"
265 // Not just using panel.panelHeight as that changes depending on the focused app.
266 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
267 }
268 Binding {
269 target: applicationsDisplayLoader.item
270 property: "interactive"
271 value: edgeDemo.stagesEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
272 }
273 Binding {
274 target: applicationsDisplayLoader.item
275 property: "spreadEnabled"
276 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
277 }
278 Binding {
279 target: applicationsDisplayLoader.item
280 property: "inverseProgress"
281 value: launcher.progress
282 }
283 Binding {
284 target: applicationsDisplayLoader.item
285 property: "orientation"
286 value: shell.orientation
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "background"
291 value: shell.background
292 }
293 }
294 }
296 InputMethod {
297 id: inputMethod
298 objectName: "inputMethod"
299 anchors { fill: parent; topMargin: panel.panelHeight }
300 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
301 }
303 Connections {
304 target: SurfaceManager
305 onSurfaceCreated: {
306 if (surface.type == MirSurfaceItem.InputMethod) {
307 inputMethod.surface = surface;
308 }
309 }
311 onSurfaceDestroyed: {
312 if (inputMethod.surface == surface) {
313 inputMethod.surface = null;
314 surface.parent = null;
315 }
316 if (!surface.parent) {
317 // there's no one displaying it. delete it right away
318 surface.release();
319 }
320 }
321 }
322 Connections {
323 target: SessionManager
324 onSessionStopping: {
325 if (!session.parentSession && !session.application) {
326 // nothing is using it. delete it right away
327 session.release();
328 }
329 }
330 }
332 Greeter {
333 id: greeter
334 objectName: "greeter"
336 hides: [launcher, panel.indicators]
337 tabletMode: shell.sideStageEnabled
338 launcherOffset: launcher.progress
339 forcedUnlock: edgeDemo.running
340 background: shell.background
342 anchors.fill: parent
343 anchors.topMargin: panel.panelHeight
345 // avoid overlapping with Launcher's edge drag area
346 // FIXME: Fix TouchRegistry & friends and remove this workaround
347 // Issue involves launcher's DDA getting disabled on a long
348 // left-edge drag
349 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
351 onSessionStarted: {
352 launcher.hide();
353 }
355 onTease: launcher.tease()
357 onEmergencyCall: startLockedApp("dialer-app")
359 Timer {
360 // See powerConnection for why this is useful
361 id: showGreeterDelayed
362 interval: 1
363 onTriggered: {
364 greeter.showNow();
365 }
366 }
368 Binding {
369 target: ApplicationManager
370 property: "suspended"
371 value: greeter.shown
372 }
373 }
375 Connections {
376 id: callConnection
377 target: callManager
379 onHasCallsChanged: {
380 if (greeter.locked && callManager.hasCalls) {
381 // We just received an incoming call while locked. The
382 // indicator will have already launched dialer-app for us, but
383 // there is a race between "hasCalls" changing and the dialer
384 // starting up. So in case we lose that race, we'll start/
385 // focus the dialer ourselves here too. Even if the indicator
386 // didn't launch the dialer for some reason (or maybe a call
387 // started via some other means), if an active call is
388 // happening, we want to be in the dialer.
389 startLockedApp("dialer-app")
390 }
391 }
392 }
394 Connections {
395 id: powerConnection
396 target: Powerd
398 onStatusChanged: {
399 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
400 !callManager.hasCalls && !edgeDemo.running) {
401 // We don't want to simply call greeter.showNow() here, because
402 // that will take too long. Qt will delay button event
403 // handling until the greeter is done loading and may think the
404 // user held down the power button the whole time, leading to a
405 // power dialog being shown. Instead, delay showing the
406 // greeter until we've finished handling the event. We could
407 // make the greeter load asynchronously instead, but that
408 // introduces a whole host of timing issues, especially with
409 // its animations. So this is simpler.
410 showGreeterDelayed.start();
411 }
412 }
413 }
415 function showHome() {
416 if (edgeDemo.running) {
417 return
418 }
420 greeter.notifyAboutToFocusApp("unity8-dash");
422 var animate = !LightDM.Greeter.active && !stages.shown
423 dash.setCurrentScope(0, animate, false)
424 ApplicationManager.requestFocusApplication("unity8-dash")
425 }
427 function showDash() {
428 if (greeter.active) {
429 greeter.notifyShowingDashFromDrag();
430 launcher.fadeOut();
431 }
433 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
434 ApplicationManager.requestFocusApplication("unity8-dash")
435 launcher.fadeOut();
436 }
437 }
439 Item {
440 id: overlay
441 z: 10
443 anchors.fill: parent
445 Panel {
446 id: panel
447 objectName: "panel"
448 anchors.fill: parent //because this draws indicator menus
449 indicators {
450 hides: [launcher]
451 available: edgeDemo.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
452 contentEnabled: edgeDemo.panelContentEnabled
453 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
455 minimizedPanelHeight: units.gu(3)
456 expandedPanelHeight: units.gu(7)
458 indicatorsModel: visibleIndicators.model
459 }
461 VisibleIndicators {
462 id: visibleIndicators
463 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
464 Component.onCompleted: initialise(indicatorProfile)
465 }
466 callHint {
467 greeterShown: greeter.shown
468 }
470 property bool topmostApplicationIsFullscreen:
471 ApplicationManager.focusedApplicationId &&
472 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
474 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
475 || greeter.hasLockedApp
476 }
478 Launcher {
479 id: launcher
480 objectName: "launcher"
482 readonly property bool dashSwipe: progress > 0
484 anchors.top: parent.top
485 anchors.topMargin: inverted ? 0 : panel.panelHeight
486 anchors.bottom: parent.bottom
487 width: parent.width
488 dragAreaWidth: shell.edgeSize
489 available: edgeDemo.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
490 inverted: usageModeSettings.usageMode === "Staged"
492 onShowDashHome: showHome()
493 onDash: showDash()
494 onDashSwipeChanged: {
495 if (dashSwipe) {
496 dash.setCurrentScope(0, false, true)
497 }
498 }
499 onLauncherApplicationSelected: {
500 if (!edgeDemo.running) {
501 greeter.notifyAboutToFocusApp(appId);
502 shell.activateApplication(appId)
503 }
504 }
505 onShownChanged: {
506 if (shown) {
507 panel.indicators.hide()
508 }
509 }
510 }
512 Wizard {
513 id: wizard
514 anchors.fill: parent
515 background: shell.background
516 }
518 Rectangle {
519 id: modalNotificationBackground
521 visible: notifications.useModal && (notifications.state == "narrow")
522 color: "#000000"
523 anchors.fill: parent
524 opacity: 0.9
526 MouseArea {
527 anchors.fill: parent
528 }
529 }
531 Notifications {
532 id: notifications
534 model: NotificationBackend.Model
535 margin: units.gu(1)
537 y: topmostIsFullscreen ? 0 : panel.panelHeight
538 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
540 states: [
541 State {
542 name: "narrow"
543 when: overlay.width <= units.gu(60)
544 AnchorChanges {
545 target: notifications
546 anchors.left: parent.left
547 anchors.right: parent.right
548 }
549 },
550 State {
551 name: "wide"
552 when: overlay.width > units.gu(60)
553 AnchorChanges {
554 target: notifications
555 anchors.left: undefined
556 anchors.right: parent.right
557 }
558 PropertyChanges { target: notifications; width: units.gu(38) }
559 }
560 ]
561 }
562 }
564 Dialogs {
565 id: dialogs
566 anchors.fill: parent
567 z: overlay.z + 10
568 onPowerOffClicked: {
569 shutdownFadeOutRectangle.enabled = true;
570 shutdownFadeOutRectangle.visible = true;
571 shutdownFadeOut.start();
572 }
573 }
575 EdgeDemo {
576 id: edgeDemo
577 objectName: "edgeDemo"
578 z: dialogs.z + 10
579 paused: Powerd.status === Powerd.Off || wizard.active // Saves power
580 greeter: greeter
581 launcher: launcher
582 panel: panel
583 stages: stages
584 }
586 Connections {
587 target: SessionBroadcast
588 onShowHome: showHome()
589 }
591 Rectangle {
592 id: shutdownFadeOutRectangle
593 z: edgeDemo.z + 10
594 enabled: false
595 visible: false
596 color: "black"
597 anchors.fill: parent
598 opacity: 0.0
599 NumberAnimation on opacity {
600 id: shutdownFadeOut
601 from: 0.0
602 to: 1.0
603 onStopped: {
604 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
605 DBusUnitySessionService.Shutdown();
606 }
607 }
608 }
609 }