on e6fa1b8 help sign in
unity8/qml/Shell.qml
637 lines 22 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 ScreenGrabber {
154 id: screenGrabber
155 z: dialogs.z + 10
156 enabled: Powerd.status === Powerd.On
157 }
159 Binding {
160 target: ApplicationManager
161 property: "forceDashActive"
162 value: launcher.shown || launcher.dashSwipe
163 }
165 VolumeKeyFilter {
166 id: volumeKeyFilter
167 onVolumeDownPressed: volumeControl.volumeDown()
168 onVolumeUpPressed: volumeControl.volumeUp()
169 onBothVolumeKeysPressed: screenGrabber.capture()
170 }
172 WindowKeysFilter {
173 Keys.onPressed: {
174 // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
175 if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
176 event.accepted = callManager.handleMediaKey(false);
177 } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
178 // FIXME: We only consider power key presses if the screen is
179 // on because of bugs 1410830/1409003. The theory is that when
180 // those bugs are encountered, there is a >2s delay between the
181 // power press event and the power release event, which causes
182 // the shutdown dialog to appear on resume. So to avoid that
183 // symptom while we investigate the root cause, we simply won't
184 // initiate any dialogs when the screen is off.
185 if (Powerd.status === Powerd.On) {
186 dialogs.onPowerKeyPressed();
187 }
188 event.accepted = true;
189 } else {
190 volumeKeyFilter.onKeyPressed(event.key);
191 event.accepted = false;
192 }
193 }
195 Keys.onReleased: {
196 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
197 dialogs.onPowerKeyReleased();
198 event.accepted = true;
199 } else {
200 volumeKeyFilter.onKeyReleased(event.key);
201 event.accepted = false;
202 }
203 }
204 }
206 Item {
207 id: stages
208 objectName: "stages"
209 width: parent.width
210 height: parent.height
211 visible: !ApplicationManager.empty
213 Connections {
214 target: ApplicationManager
216 // This signal is also fired when we try to focus the current app
217 // again. We rely on this!
218 onFocusedApplicationIdChanged: {
219 var appId = ApplicationManager.focusedApplicationId;
221 if (tutorial.running && appId != "unity8-dash") {
222 // If this happens on first boot, we may be in edge
223 // tutorial or wizard while receiving a call. But a call
224 // is more important than wizard so just bail out of those.
225 tutorial.finish();
226 wizard.hide();
227 }
229 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
230 // If we are in the middle of a call, make dialer lockedApp and show it.
231 // This can happen if user backs out of dialer back to greeter, then
232 // launches dialer again.
233 greeter.lockedApp = appId;
234 }
235 greeter.notifyAppFocused(appId);
237 panel.indicators.hide();
238 launcher.hide();
239 }
240 }
242 Loader {
243 id: applicationsDisplayLoader
244 objectName: "applicationsDisplayLoader"
245 anchors.fill: parent
247 // When we have a locked app, we only want to show that one app.
248 // FIXME: do this in a less traumatic way. We currently only allow
249 // locked apps in phone mode (see FIXME in Lockscreen component in
250 // this same file). When that changes, we need to do something
251 // nicer here. But this code is currently just to prevent a
252 // theoretical attack where user enters lockedApp mode, then makes
253 // the screen larger (maybe connects to monitor) and tries to enter
254 // tablet mode.
255 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
256 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
257 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
259 property bool interactive: tutorial.spreadEnabled
260 && !greeter.shown
261 && panel.indicators.fullyClosed
262 && launcher.progress == 0
263 && !notifications.useModal
265 onInteractiveChanged: { if (interactive) { focus = true; } }
267 Binding {
268 target: applicationsDisplayLoader.item
269 property: "objectName"
270 value: "stage"
271 }
272 Binding {
273 target: applicationsDisplayLoader.item
274 property: "dragAreaWidth"
275 value: shell.edgeSize
276 }
277 Binding {
278 target: applicationsDisplayLoader.item
279 property: "maximizedAppTopMargin"
280 // Not just using panel.panelHeight as that changes depending on the focused app.
281 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
282 }
283 Binding {
284 target: applicationsDisplayLoader.item
285 property: "interactive"
286 value: applicationsDisplayLoader.interactive
287 }
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "spreadEnabled"
291 value: tutorial.spreadEnabled && !greeter.hasLockedApp
292 }
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "inverseProgress"
296 value: launcher.progress
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "orientation"
301 value: shell.orientation
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "background"
306 value: shell.background
307 }
308 }
309 }
311 InputMethod {
312 id: inputMethod
313 objectName: "inputMethod"
314 anchors { fill: parent; topMargin: panel.panelHeight }
315 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
316 }
318 Connections {
319 target: SurfaceManager
320 onSurfaceCreated: {
321 if (surface.type == MirSurfaceItem.InputMethod) {
322 inputMethod.surface = surface;
323 }
324 }
326 onSurfaceDestroyed: {
327 if (inputMethod.surface == surface) {
328 inputMethod.surface = null;
329 surface.parent = null;
330 }
331 if (!surface.parent) {
332 // there's no one displaying it. delete it right away
333 surface.release();
334 }
335 }
336 }
337 Connections {
338 target: SessionManager
339 onSessionStopping: {
340 if (!session.parentSession && !session.application) {
341 // nothing is using it. delete it right away
342 session.release();
343 }
344 }
345 }
347 Greeter {
348 id: greeter
349 objectName: "greeter"
351 hides: [launcher, panel.indicators]
352 tabletMode: shell.sideStageEnabled
353 launcherOffset: launcher.progress
354 forcedUnlock: tutorial.running
355 background: shell.background
357 anchors.fill: parent
358 anchors.topMargin: panel.panelHeight
360 // avoid overlapping with Launcher's edge drag area
361 // FIXME: Fix TouchRegistry & friends and remove this workaround
362 // Issue involves launcher's DDA getting disabled on a long
363 // left-edge drag
364 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
366 onSessionStarted: {
367 launcher.hide();
368 }
370 onTease: {
371 if (!tutorial.running) {
372 launcher.tease();
373 }
374 }
376 onEmergencyCall: startLockedApp("dialer-app")
378 Timer {
379 // See powerConnection for why this is useful
380 id: showGreeterDelayed
381 interval: 1
382 onTriggered: {
383 greeter.forceShow();
384 }
385 }
387 Binding {
388 target: ApplicationManager
389 property: "suspended"
390 value: greeter.shown
391 }
392 }
394 Connections {
395 id: callConnection
396 target: callManager
398 onHasCallsChanged: {
399 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
400 // We just received an incoming call while locked. The
401 // indicator will have already launched dialer-app for us, but
402 // there is a race between "hasCalls" changing and the dialer
403 // starting up. So in case we lose that race, we'll start/
404 // focus the dialer ourselves here too. Even if the indicator
405 // didn't launch the dialer for some reason (or maybe a call
406 // started via some other means), if an active call is
407 // happening, we want to be in the dialer.
408 startLockedApp("dialer-app")
409 }
410 }
411 }
413 Connections {
414 id: powerConnection
415 target: Powerd
417 onStatusChanged: {
418 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
419 !callManager.hasCalls && !tutorial.running) {
420 // We don't want to simply call greeter.showNow() here, because
421 // that will take too long. Qt will delay button event
422 // handling until the greeter is done loading and may think the
423 // user held down the power button the whole time, leading to a
424 // power dialog being shown. Instead, delay showing the
425 // greeter until we've finished handling the event. We could
426 // make the greeter load asynchronously instead, but that
427 // introduces a whole host of timing issues, especially with
428 // its animations. So this is simpler.
429 showGreeterDelayed.start();
430 }
431 }
432 }
434 function showHome() {
435 if (tutorial.running) {
436 return
437 }
439 greeter.notifyAboutToFocusApp("unity8-dash");
441 var animate = !LightDM.Greeter.active && !stages.shown
442 dash.setCurrentScope(0, animate, false)
443 ApplicationManager.requestFocusApplication("unity8-dash")
444 }
446 function showDash() {
447 if (greeter.active) {
448 greeter.notifyShowingDashFromDrag();
449 launcher.fadeOut();
450 }
452 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
453 ApplicationManager.requestFocusApplication("unity8-dash")
454 launcher.fadeOut();
455 }
456 }
458 Item {
459 id: overlay
460 z: 10
462 anchors.fill: parent
464 Panel {
465 id: panel
466 objectName: "panel"
467 anchors.fill: parent //because this draws indicator menus
468 indicators {
469 hides: [launcher]
470 available: tutorial.panelEnabled
471 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
472 && !greeter.hasLockedApp
473 contentEnabled: tutorial.panelContentEnabled
474 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
476 minimizedPanelHeight: units.gu(3)
477 expandedPanelHeight: units.gu(7)
479 indicatorsModel: Indicators.IndicatorsModel {
480 Component.onCompleted: load(indicatorProfile);
481 }
482 }
483 callHint {
484 greeterShown: greeter.shown
485 }
487 property bool topmostApplicationIsFullscreen:
488 ApplicationManager.focusedApplicationId &&
489 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
491 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
492 || greeter.hasLockedApp
493 }
495 Launcher {
496 id: launcher
497 objectName: "launcher"
499 readonly property bool dashSwipe: progress > 0
501 anchors.top: parent.top
502 anchors.topMargin: inverted ? 0 : panel.panelHeight
503 anchors.bottom: parent.bottom
504 width: parent.width
505 dragAreaWidth: shell.edgeSize
506 available: tutorial.launcherEnabled
507 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
508 && !greeter.hasLockedApp
509 inverted: usageModeSettings.usageMode === "Staged"
510 shadeBackground: !tutorial.running
512 onShowDashHome: showHome()
513 onDash: showDash()
514 onDashSwipeChanged: {
515 if (dashSwipe) {
516 dash.setCurrentScope(0, false, true)
517 }
518 }
519 onLauncherApplicationSelected: {
520 if (!tutorial.running) {
521 greeter.notifyAboutToFocusApp(appId);
522 shell.activateApplication(appId)
523 }
524 }
525 onShownChanged: {
526 if (shown) {
527 panel.indicators.hide()
528 }
529 }
530 }
532 Wizard {
533 id: wizard
534 anchors.fill: parent
535 background: shell.background
536 }
538 Rectangle {
539 id: modalNotificationBackground
541 visible: notifications.useModal && (notifications.state == "narrow")
542 color: "#000000"
543 anchors.fill: parent
544 opacity: 0.9
546 MouseArea {
547 anchors.fill: parent
548 }
549 }
551 Notifications {
552 id: notifications
554 model: NotificationBackend.Model
555 margin: units.gu(1)
557 y: topmostIsFullscreen ? 0 : panel.panelHeight
558 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
560 states: [
561 State {
562 name: "narrow"
563 when: overlay.width <= units.gu(60)
564 AnchorChanges {
565 target: notifications
566 anchors.left: parent.left
567 anchors.right: parent.right
568 }
569 },
570 State {
571 name: "wide"
572 when: overlay.width > units.gu(60)
573 AnchorChanges {
574 target: notifications
575 anchors.left: undefined
576 anchors.right: parent.right
577 }
578 PropertyChanges { target: notifications; width: units.gu(38) }
579 }
580 ]
581 }
582 }
584 Dialogs {
585 id: dialogs
586 anchors.fill: parent
587 z: overlay.z + 10
588 onPowerOffClicked: {
589 shutdownFadeOutRectangle.enabled = true;
590 shutdownFadeOutRectangle.visible = true;
591 shutdownFadeOut.start();
592 }
593 }
595 Tutorial {
596 id: tutorial
597 objectName: "tutorial"
598 active: AccountsService.demoEdges
599 paused: LightDM.Greeter.active
600 launcher: launcher
601 panel: panel
602 stages: stages
603 overlay: overlay
604 edgeSize: shell.edgeSize
606 onFinished: {
607 AccountsService.demoEdges = false;
608 active = false; // for immediate response / if AS is having problems
609 }
610 }
612 Connections {
613 target: SessionBroadcast
614 onShowHome: showHome()
615 }
617 Rectangle {
618 id: shutdownFadeOutRectangle
619 z: screenGrabber.z + 10
620 enabled: false
621 visible: false
622 color: "black"
623 anchors.fill: parent
624 opacity: 0.0
625 NumberAnimation on opacity {
626 id: shutdownFadeOut
627 from: 0.0
628 to: 1.0
629 onStopped: {
630 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
631 DBusUnitySessionService.Shutdown();
632 }
633 }
634 }
635 }