on bc1218a help sign in
unity8/qml/Shell.qml
617 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 "Wizard"
38import Unity.Notifications 1.0 as NotificationBackend
39import Unity.Session 0.1
40import Unity.DashCommunicator 0.1
41import Unity.Indicators 0.1 as Indicators
43Item {
44 id: shell
46 // Disable everything while greeter is waiting, so that the user can't swipe
47 // the greeter or launcher until we know whether the session is locked.
48 enabled: !greeter.waiting
50 // this is only here to select the width / height of the window if not running fullscreen
51 property bool tablet: false
52 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
53 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
55 property real edgeSize: units.gu(2)
56 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
57 property url background: asImageTester.status == Image.Ready ? asImageTester.source
58 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
59 readonly property real panelHeight: panel.panelHeight
61 property bool sideStageEnabled: shell.width >= units.gu(100)
62 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
64 property int orientation
65 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
66 onDeviceOrientationAngleChanged: {
67 if (!OrientationLock.enabled) {
68 orientation = Screen.orientation;
69 }
70 }
71 readonly property bool orientationLockEnabled: OrientationLock.enabled
72 onOrientationLockEnabledChanged: {
73 if (orientationLockEnabled) {
74 OrientationLock.savedOrientation = Screen.orientation;
75 } else {
76 orientation = Screen.orientation;
77 }
78 }
80 function activateApplication(appId) {
81 if (ApplicationManager.findApplication(appId)) {
82 ApplicationManager.requestFocusApplication(appId);
83 } else {
84 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
85 ApplicationManager.startApplication(appId, execFlags);
86 }
87 }
89 function startLockedApp(app) {
90 if (greeter.locked) {
91 greeter.lockedApp = app;
92 }
93 shell.activateApplication(app);
94 }
96 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
97 Image {
98 id: asImageTester
99 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
100 height: 0
101 width: 0
102 sourceSize.height: 0
103 sourceSize.width: 0
104 }
106 GSettings {
107 id: backgroundSettings
108 objectName: "backgroundSettings"
109 schema.id: "org.gnome.desktop.background"
110 }
112 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
113 Image {
114 id: gsImageTester
115 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
116 height: 0
117 width: 0
118 sourceSize.height: 0
119 sourceSize.width: 0
120 }
122 GSettings {
123 id: usageModeSettings
124 schema.id: "com.canonical.Unity8"
125 }
127 Binding {
128 target: LauncherModel
129 property: "applicationManager"
130 value: ApplicationManager
131 }
133 Component.onCompleted: {
134 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
135 if (ApplicationManager.count > 0) {
136 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
137 }
138 if (orientationLockEnabled) {
139 orientation = OrientationLock.savedOrientation;
140 }
141 }
143 VolumeControl {
144 id: volumeControl
145 }
147 DashCommunicator {
148 id: dash
149 objectName: "dashCommunicator"
150 }
152 ScreenGrabber {
153 id: screenGrabber
154 z: edgeDemo.z + 10
155 enabled: Powerd.status === Powerd.On
156 }
158 Binding {
159 target: ApplicationManager
160 property: "forceDashActive"
161 value: launcher.shown || launcher.dashSwipe
162 }
164 VolumeKeyFilter {
165 id: volumeKeyFilter
166 onVolumeDownPressed: volumeControl.volumeDown()
167 onVolumeUpPressed: volumeControl.volumeUp()
168 onBothVolumeKeysPressed: screenGrabber.capture()
169 }
171 WindowKeysFilter {
172 Keys.onPressed: {
173 // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
174 if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
175 event.accepted = callManager.handleMediaKey(false);
176 } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
177 // FIXME: We only consider power key presses if the screen is
178 // on because of bugs 1410830/1409003. The theory is that when
179 // those bugs are encountered, there is a >2s delay between the
180 // power press event and the power release event, which causes
181 // the shutdown dialog to appear on resume. So to avoid that
182 // symptom while we investigate the root cause, we simply won't
183 // initiate any dialogs when the screen is off.
184 if (Powerd.status === Powerd.On) {
185 dialogs.onPowerKeyPressed();
186 }
187 event.accepted = true;
188 } else {
189 volumeKeyFilter.onKeyPressed(event.key);
190 event.accepted = false;
191 }
192 }
194 Keys.onReleased: {
195 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
196 dialogs.onPowerKeyReleased();
197 event.accepted = true;
198 } else {
199 volumeKeyFilter.onKeyReleased(event.key);
200 event.accepted = false;
201 }
202 }
203 }
205 Item {
206 id: stages
207 objectName: "stages"
208 width: parent.width
209 height: parent.height
210 visible: !ApplicationManager.empty
212 Connections {
213 target: ApplicationManager
215 onFocusRequested: {
216 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
217 // If we are in the middle of a call, make dialer lockedApp and show it.
218 // This can happen if user backs out of dialer back to greeter, then
219 // launches dialer again.
220 greeter.lockedApp = appId;
221 }
223 greeter.notifyAppFocused(appId);
224 }
226 onFocusedApplicationIdChanged: {
227 greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
228 panel.indicators.hide();
229 }
231 onApplicationAdded: {
232 if (edgeDemo.running && appId != "unity8-dash") {
233 // If this happens on first boot, we may be in edge
234 // tutorial or wizard while receiving a call. But a call
235 // is more important than wizard so just bail out of those.
236 edgeDemo.hideEdgeDemos();
237 wizard.hide();
238 }
240 greeter.notifyAppFocused(appId);
241 launcher.hide();
242 }
243 }
245 Loader {
246 id: applicationsDisplayLoader
247 objectName: "applicationsDisplayLoader"
248 anchors.fill: parent
250 // When we have a locked app, we only want to show that one app.
251 // FIXME: do this in a less traumatic way. We currently only allow
252 // locked apps in phone mode (see FIXME in Lockscreen component in
253 // this same file). When that changes, we need to do something
254 // nicer here. But this code is currently just to prevent a
255 // theoretical attack where user enters lockedApp mode, then makes
256 // the screen larger (maybe connects to monitor) and tries to enter
257 // tablet mode.
258 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
259 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
260 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
262 Binding {
263 target: applicationsDisplayLoader.item
264 property: "objectName"
265 value: "stage"
266 }
267 Binding {
268 target: applicationsDisplayLoader.item
269 property: "dragAreaWidth"
270 value: shell.edgeSize
271 }
272 Binding {
273 target: applicationsDisplayLoader.item
274 property: "maximizedAppTopMargin"
275 // Not just using panel.panelHeight as that changes depending on the focused app.
276 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
277 }
278 Binding {
279 target: applicationsDisplayLoader.item
280 property: "interactive"
281 value: edgeDemo.stagesEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
282 }
283 Binding {
284 target: applicationsDisplayLoader.item
285 property: "spreadEnabled"
286 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "inverseProgress"
291 value: launcher.progress
292 }
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "orientation"
296 value: shell.orientation
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "background"
301 value: shell.background
302 }
303 }
304 }
306 InputMethod {
307 id: inputMethod
308 objectName: "inputMethod"
309 anchors { fill: parent; topMargin: panel.panelHeight }
310 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
311 }
313 Connections {
314 target: SurfaceManager
315 onSurfaceCreated: {
316 if (surface.type == MirSurfaceItem.InputMethod) {
317 inputMethod.surface = surface;
318 }
319 }
321 onSurfaceDestroyed: {
322 if (inputMethod.surface == surface) {
323 inputMethod.surface = null;
324 surface.parent = null;
325 }
326 if (!surface.parent) {
327 // there's no one displaying it. delete it right away
328 surface.release();
329 }
330 }
331 }
332 Connections {
333 target: SessionManager
334 onSessionStopping: {
335 if (!session.parentSession && !session.application) {
336 // nothing is using it. delete it right away
337 session.release();
338 }
339 }
340 }
342 Greeter {
343 id: greeter
344 objectName: "greeter"
346 hides: [launcher, panel.indicators]
347 tabletMode: shell.sideStageEnabled
348 launcherOffset: launcher.progress
349 forcedUnlock: edgeDemo.running
350 background: shell.background
352 anchors.fill: parent
353 anchors.topMargin: panel.panelHeight
355 // avoid overlapping with Launcher's edge drag area
356 // FIXME: Fix TouchRegistry & friends and remove this workaround
357 // Issue involves launcher's DDA getting disabled on a long
358 // left-edge drag
359 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
361 onSessionStarted: {
362 launcher.hide();
363 }
365 onTease: launcher.tease()
367 onEmergencyCall: startLockedApp("dialer-app")
369 Timer {
370 // See powerConnection for why this is useful
371 id: showGreeterDelayed
372 interval: 1
373 onTriggered: {
374 greeter.forceShow();
375 }
376 }
378 Binding {
379 target: ApplicationManager
380 property: "suspended"
381 value: greeter.shown
382 }
383 }
385 Connections {
386 id: callConnection
387 target: callManager
389 onHasCallsChanged: {
390 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
391 // We just received an incoming call while locked. The
392 // indicator will have already launched dialer-app for us, but
393 // there is a race between "hasCalls" changing and the dialer
394 // starting up. So in case we lose that race, we'll start/
395 // focus the dialer ourselves here too. Even if the indicator
396 // didn't launch the dialer for some reason (or maybe a call
397 // started via some other means), if an active call is
398 // happening, we want to be in the dialer.
399 startLockedApp("dialer-app")
400 }
401 }
402 }
404 Connections {
405 id: powerConnection
406 target: Powerd
408 onStatusChanged: {
409 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
410 !callManager.hasCalls && !edgeDemo.running) {
411 // We don't want to simply call greeter.showNow() here, because
412 // that will take too long. Qt will delay button event
413 // handling until the greeter is done loading and may think the
414 // user held down the power button the whole time, leading to a
415 // power dialog being shown. Instead, delay showing the
416 // greeter until we've finished handling the event. We could
417 // make the greeter load asynchronously instead, but that
418 // introduces a whole host of timing issues, especially with
419 // its animations. So this is simpler.
420 showGreeterDelayed.start();
421 }
422 }
423 }
425 function showHome() {
426 if (edgeDemo.running) {
427 return
428 }
430 greeter.notifyAboutToFocusApp("unity8-dash");
432 var animate = !LightDM.Greeter.active && !stages.shown
433 dash.setCurrentScope(0, animate, false)
434 ApplicationManager.requestFocusApplication("unity8-dash")
435 }
437 function showDash() {
438 if (greeter.active) {
439 greeter.notifyShowingDashFromDrag();
440 launcher.fadeOut();
441 }
443 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
444 ApplicationManager.requestFocusApplication("unity8-dash")
445 launcher.fadeOut();
446 }
447 }
449 Item {
450 id: overlay
451 z: 10
453 anchors.fill: parent
455 Panel {
456 id: panel
457 objectName: "panel"
458 anchors.fill: parent //because this draws indicator menus
459 indicators {
460 hides: [launcher]
461 available: edgeDemo.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
462 contentEnabled: edgeDemo.panelContentEnabled
463 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
465 minimizedPanelHeight: units.gu(3)
466 expandedPanelHeight: units.gu(7)
468 indicatorsModel: Indicators.IndicatorsModel {
469 Component.onCompleted: load(indicatorProfile);
470 }
471 }
472 callHint {
473 greeterShown: greeter.shown
474 }
476 property bool topmostApplicationIsFullscreen:
477 ApplicationManager.focusedApplicationId &&
478 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
480 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
481 || greeter.hasLockedApp
482 }
484 Launcher {
485 id: launcher
486 objectName: "launcher"
488 readonly property bool dashSwipe: progress > 0
490 anchors.top: parent.top
491 anchors.topMargin: inverted ? 0 : panel.panelHeight
492 anchors.bottom: parent.bottom
493 width: parent.width
494 dragAreaWidth: shell.edgeSize
495 available: edgeDemo.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
496 inverted: usageModeSettings.usageMode === "Staged"
498 onShowDashHome: showHome()
499 onDash: showDash()
500 onDashSwipeChanged: {
501 if (dashSwipe) {
502 dash.setCurrentScope(0, false, true)
503 }
504 }
505 onLauncherApplicationSelected: {
506 if (!edgeDemo.running) {
507 greeter.notifyAboutToFocusApp(appId);
508 shell.activateApplication(appId)
509 }
510 }
511 onShownChanged: {
512 if (shown) {
513 panel.indicators.hide()
514 }
515 }
516 }
518 Wizard {
519 id: wizard
520 anchors.fill: parent
521 background: shell.background
522 }
524 Rectangle {
525 id: modalNotificationBackground
527 visible: notifications.useModal && (notifications.state == "narrow")
528 color: "#000000"
529 anchors.fill: parent
530 opacity: 0.9
532 MouseArea {
533 anchors.fill: parent
534 }
535 }
537 Notifications {
538 id: notifications
540 model: NotificationBackend.Model
541 margin: units.gu(1)
543 y: topmostIsFullscreen ? 0 : panel.panelHeight
544 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
546 states: [
547 State {
548 name: "narrow"
549 when: overlay.width <= units.gu(60)
550 AnchorChanges {
551 target: notifications
552 anchors.left: parent.left
553 anchors.right: parent.right
554 }
555 },
556 State {
557 name: "wide"
558 when: overlay.width > units.gu(60)
559 AnchorChanges {
560 target: notifications
561 anchors.left: undefined
562 anchors.right: parent.right
563 }
564 PropertyChanges { target: notifications; width: units.gu(38) }
565 }
566 ]
567 }
568 }
570 Dialogs {
571 id: dialogs
572 anchors.fill: parent
573 z: overlay.z + 10
574 onPowerOffClicked: {
575 shutdownFadeOutRectangle.enabled = true;
576 shutdownFadeOutRectangle.visible = true;
577 shutdownFadeOut.start();
578 }
579 }
581 EdgeDemo {
582 id: edgeDemo
583 objectName: "edgeDemo"
584 z: dialogs.z + 10
585 paused: Powerd.status === Powerd.Off || wizard.active // Saves power
586 greeter: greeter
587 launcher: launcher
588 panel: panel
589 stages: stages
590 }
592 Connections {
593 target: SessionBroadcast
594 onShowHome: showHome()
595 }
597 Rectangle {
598 id: shutdownFadeOutRectangle
599 z: edgeDemo.z + 10
600 enabled: false
601 visible: false
602 color: "black"
603 anchors.fill: parent
604 opacity: 0.0
605 NumberAnimation on opacity {
606 id: shutdownFadeOut
607 from: 0.0
608 to: 1.0
609 onStopped: {
610 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
611 DBusUnitySessionService.Shutdown();
612 }
613 }
614 }
615 }