on 558e6ae help sign in
unity8/qml/Shell.qml
619 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 "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 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 // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
176 if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
177 event.accepted = callManager.handleMediaKey(false);
178 } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
179 // FIXME: We only consider power key presses if the screen is
180 // on because of bugs 1410830/1409003. The theory is that when
181 // those bugs are encountered, there is a >2s delay between the
182 // power press event and the power release event, which causes
183 // the shutdown dialog to appear on resume. So to avoid that
184 // symptom while we investigate the root cause, we simply won't
185 // initiate any dialogs when the screen is off.
186 if (Powerd.status === Powerd.On) {
187 dialogs.onPowerKeyPressed();
188 }
189 event.accepted = true;
190 } else {
191 volumeKeyFilter.onKeyPressed(event.key);
192 event.accepted = false;
193 }
194 }
196 Keys.onReleased: {
197 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
198 dialogs.onPowerKeyReleased();
199 event.accepted = true;
200 } else {
201 volumeKeyFilter.onKeyReleased(event.key);
202 event.accepted = false;
203 }
204 }
205 }
207 Item {
208 id: stages
209 objectName: "stages"
210 width: parent.width
211 height: parent.height
212 visible: !ApplicationManager.empty
214 Connections {
215 target: ApplicationManager
217 onFocusRequested: {
218 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
219 // If we are in the middle of a call, make dialer lockedApp and show it.
220 // This can happen if user backs out of dialer back to greeter, then
221 // launches dialer again.
222 greeter.lockedApp = appId;
223 }
225 greeter.notifyAppFocused(appId);
226 }
228 onFocusedApplicationIdChanged: {
229 greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
230 panel.indicators.hide();
231 }
233 onApplicationAdded: {
234 if (edgeDemo.running && appId != "unity8-dash") {
235 // If this happens on first boot, we may be in edge
236 // tutorial or wizard while receiving a call. But a call
237 // is more important than wizard so just bail out of those.
238 edgeDemo.hideEdgeDemos();
239 wizard.hide();
240 }
242 greeter.notifyAppFocused(appId);
243 launcher.hide();
244 }
245 }
247 Loader {
248 id: applicationsDisplayLoader
249 objectName: "applicationsDisplayLoader"
250 anchors.fill: parent
252 // When we have a locked app, we only want to show that one app.
253 // FIXME: do this in a less traumatic way. We currently only allow
254 // locked apps in phone mode (see FIXME in Lockscreen component in
255 // this same file). When that changes, we need to do something
256 // nicer here. But this code is currently just to prevent a
257 // theoretical attack where user enters lockedApp mode, then makes
258 // the screen larger (maybe connects to monitor) and tries to enter
259 // tablet mode.
260 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
261 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
262 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
264 Binding {
265 target: applicationsDisplayLoader.item
266 property: "objectName"
267 value: "stage"
268 }
269 Binding {
270 target: applicationsDisplayLoader.item
271 property: "dragAreaWidth"
272 value: shell.edgeSize
273 }
274 Binding {
275 target: applicationsDisplayLoader.item
276 property: "maximizedAppTopMargin"
277 // Not just using panel.panelHeight as that changes depending on the focused app.
278 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "interactive"
283 value: edgeDemo.stagesEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
284 }
285 Binding {
286 target: applicationsDisplayLoader.item
287 property: "spreadEnabled"
288 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
289 }
290 Binding {
291 target: applicationsDisplayLoader.item
292 property: "inverseProgress"
293 value: launcher.progress
294 }
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "orientation"
298 value: shell.orientation
299 }
300 Binding {
301 target: applicationsDisplayLoader.item
302 property: "background"
303 value: shell.background
304 }
305 }
306 }
308 InputMethod {
309 id: inputMethod
310 objectName: "inputMethod"
311 anchors { fill: parent; topMargin: panel.panelHeight }
312 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
313 }
315 Connections {
316 target: SurfaceManager
317 onSurfaceCreated: {
318 if (surface.type == MirSurfaceItem.InputMethod) {
319 inputMethod.surface = surface;
320 }
321 }
323 onSurfaceDestroyed: {
324 if (inputMethod.surface == surface) {
325 inputMethod.surface = null;
326 surface.parent = null;
327 }
328 if (!surface.parent) {
329 // there's no one displaying it. delete it right away
330 surface.release();
331 }
332 }
333 }
334 Connections {
335 target: SessionManager
336 onSessionStopping: {
337 if (!session.parentSession && !session.application) {
338 // nothing is using it. delete it right away
339 session.release();
340 }
341 }
342 }
344 Greeter {
345 id: greeter
346 objectName: "greeter"
348 hides: [launcher, panel.indicators]
349 tabletMode: shell.sideStageEnabled
350 launcherOffset: launcher.progress
351 forcedUnlock: edgeDemo.running
352 background: shell.background
354 anchors.fill: parent
355 anchors.topMargin: panel.panelHeight
357 // avoid overlapping with Launcher's edge drag area
358 // FIXME: Fix TouchRegistry & friends and remove this workaround
359 // Issue involves launcher's DDA getting disabled on a long
360 // left-edge drag
361 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
363 onSessionStarted: {
364 launcher.hide();
365 }
367 onTease: launcher.tease()
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.showNow();
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 && !edgeDemo.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 (edgeDemo.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.active) {
441 greeter.notifyShowingDashFromDrag();
442 launcher.fadeOut();
443 }
445 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
446 ApplicationManager.requestFocusApplication("unity8-dash")
447 launcher.fadeOut();
448 }
449 }
451 Item {
452 id: overlay
453 z: 10
455 anchors.fill: parent
457 Panel {
458 id: panel
459 objectName: "panel"
460 anchors.fill: parent //because this draws indicator menus
461 indicators {
462 hides: [launcher]
463 available: edgeDemo.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
464 contentEnabled: edgeDemo.panelContentEnabled
465 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
467 minimizedPanelHeight: units.gu(3)
468 expandedPanelHeight: units.gu(7)
470 indicatorsModel: Indicators.IndicatorsModel {
471 Component.onCompleted: load(indicatorProfile);
472 }
473 }
474 callHint {
475 greeterShown: greeter.shown
476 }
478 property bool topmostApplicationIsFullscreen:
479 ApplicationManager.focusedApplicationId &&
480 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
482 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
483 || greeter.hasLockedApp
484 }
486 Launcher {
487 id: launcher
488 objectName: "launcher"
490 readonly property bool dashSwipe: progress > 0
492 anchors.top: parent.top
493 anchors.topMargin: inverted ? 0 : panel.panelHeight
494 anchors.bottom: parent.bottom
495 width: parent.width
496 dragAreaWidth: shell.edgeSize
497 available: edgeDemo.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
498 inverted: usageModeSettings.usageMode === "Staged"
500 onShowDashHome: showHome()
501 onDash: showDash()
502 onDashSwipeChanged: {
503 if (dashSwipe) {
504 dash.setCurrentScope(0, false, true)
505 }
506 }
507 onLauncherApplicationSelected: {
508 if (!edgeDemo.running) {
509 greeter.notifyAboutToFocusApp(appId);
510 shell.activateApplication(appId)
511 }
512 }
513 onShownChanged: {
514 if (shown) {
515 panel.indicators.hide()
516 }
517 }
518 }
520 Wizard {
521 id: wizard
522 anchors.fill: parent
523 background: shell.background
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 EdgeDemo {
584 id: edgeDemo
585 objectName: "edgeDemo"
586 z: dialogs.z + 10
587 paused: Powerd.status === Powerd.Off || wizard.active // Saves power
588 greeter: greeter
589 launcher: launcher
590 panel: panel
591 stages: stages
592 }
594 Connections {
595 target: SessionBroadcast
596 onShowHome: showHome()
597 }
599 Rectangle {
600 id: shutdownFadeOutRectangle
601 z: edgeDemo.z + 10
602 enabled: false
603 visible: false
604 color: "black"
605 anchors.fill: parent
606 opacity: 0.0
607 NumberAnimation on opacity {
608 id: shutdownFadeOut
609 from: 0.0
610 to: 1.0
611 onStopped: {
612 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
613 DBusUnitySessionService.Shutdown();
614 }
615 }
616 }
617 }