on 8a93021 help sign in
unity8/qml/Shell.qml
638 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.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 "Tutorial"
39import "Wizard"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
43import Unity.Indicators 0.1 as Indicators
45Item {
46 id: shell
48 // Disable everything while greeter is waiting, so that the user can't swipe
49 // the greeter or launcher until we know whether the session is locked.
50 enabled: !greeter.waiting
52 // this is only here to select the width / height of the window if not running fullscreen
53 property bool tablet: false
54 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
55 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
57 property real edgeSize: units.gu(2)
58 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
59 property url background: asImageTester.status == Image.Ready ? asImageTester.source
60 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
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 // This is _only_ used to expose the property to autopilot tests
82 readonly property string testShellMode: shellMode
84 function activateApplication(appId) {
85 if (ApplicationManager.findApplication(appId)) {
86 ApplicationManager.requestFocusApplication(appId);
87 } else {
88 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
89 ApplicationManager.startApplication(appId, execFlags);
90 }
91 }
93 function startLockedApp(app) {
94 if (greeter.locked) {
95 greeter.lockedApp = app;
96 }
97 shell.activateApplication(app);
98 }
100 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
101 Image {
102 id: asImageTester
103 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
104 height: 0
105 width: 0
106 sourceSize.height: 0
107 sourceSize.width: 0
108 }
110 GSettings {
111 id: backgroundSettings
112 objectName: "backgroundSettings"
113 schema.id: "org.gnome.desktop.background"
114 }
116 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
117 Image {
118 id: gsImageTester
119 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
120 height: 0
121 width: 0
122 sourceSize.height: 0
123 sourceSize.width: 0
124 }
126 GSettings {
127 id: usageModeSettings
128 schema.id: "com.canonical.Unity8"
129 }
131 Binding {
132 target: LauncherModel
133 property: "applicationManager"
134 value: ApplicationManager
135 }
137 Component.onCompleted: {
138 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
139 if (ApplicationManager.count > 0) {
140 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
141 }
142 if (orientationLockEnabled) {
143 orientation = OrientationLock.savedOrientation;
144 }
145 }
147 VolumeControl {
148 id: volumeControl
149 }
151 DashCommunicator {
152 id: dash
153 objectName: "dashCommunicator"
154 }
156 PhysicalKeysMapper {
157 id: physicalKeysMapper
159 onPowerKeyLongPressed: dialogs.showPowerDialog()
160 onVolumeDownTriggered: volumeControl.volumeDown();
161 onVolumeUpTriggered: volumeControl.volumeUp();
162 onScreenshotTriggered: screenGrabber.capture();
163 }
165 ScreenGrabber {
166 id: screenGrabber
167 z: dialogs.z + 10
168 enabled: Powerd.status === Powerd.On
169 }
171 Binding {
172 target: ApplicationManager
173 property: "forceDashActive"
174 value: launcher.shown || launcher.dashSwipe
175 }
177 WindowKeysFilter {
178 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
179 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
180 }
182 HomeKeyWatcher {
183 onActivated: { launcher.fadeOut(); shell.showHome(); }
184 }
186 Item {
187 id: stages
188 objectName: "stages"
189 width: parent.width
190 height: parent.height
192 Connections {
193 target: ApplicationManager
195 // This signal is also fired when we try to focus the current app
196 // again. We rely on this!
197 onFocusedApplicationIdChanged: {
198 var appId = ApplicationManager.focusedApplicationId;
200 if (tutorial.running && appId != "" && appId != "unity8-dash") {
201 // If this happens on first boot, we may be in edge
202 // tutorial or wizard while receiving a call. But a call
203 // is more important than wizard so just bail out of those.
204 tutorial.finish();
205 wizard.hide();
206 }
208 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
209 // If we are in the middle of a call, make dialer lockedApp and show it.
210 // This can happen if user backs out of dialer back to greeter, then
211 // launches dialer again.
212 greeter.lockedApp = appId;
213 }
214 greeter.notifyAppFocused(appId);
216 panel.indicators.hide();
217 }
219 onApplicationAdded: {
220 launcher.hide();
221 }
222 }
224 Loader {
225 id: applicationsDisplayLoader
226 objectName: "applicationsDisplayLoader"
227 anchors.fill: parent
229 // When we have a locked app, we only want to show that one app.
230 // FIXME: do this in a less traumatic way. We currently only allow
231 // locked apps in phone mode (see FIXME in Lockscreen component in
232 // this same file). When that changes, we need to do something
233 // nicer here. But this code is currently just to prevent a
234 // theoretical attack where user enters lockedApp mode, then makes
235 // the screen larger (maybe connects to monitor) and tries to enter
236 // tablet mode.
237 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
238 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
239 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
241 property bool interactive: tutorial.spreadEnabled
242 && !greeter.shown
243 && panel.indicators.fullyClosed
244 && launcher.progress == 0
245 && !notifications.useModal
247 onInteractiveChanged: { if (interactive) { focus = true; } }
249 Binding {
250 target: applicationsDisplayLoader.item
251 property: "objectName"
252 value: "stage"
253 }
254 Binding {
255 target: applicationsDisplayLoader.item
256 property: "dragAreaWidth"
257 value: shell.edgeSize
258 }
259 Binding {
260 target: applicationsDisplayLoader.item
261 property: "maximizedAppTopMargin"
262 // Not just using panel.panelHeight as that changes depending on the focused app.
263 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
264 }
265 Binding {
266 target: applicationsDisplayLoader.item
267 property: "interactive"
268 value: applicationsDisplayLoader.interactive
269 }
270 Binding {
271 target: applicationsDisplayLoader.item
272 property: "spreadEnabled"
273 value: tutorial.spreadEnabled && !greeter.hasLockedApp
274 }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "inverseProgress"
278 value: greeter.locked ? 0 : launcher.progress
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "orientation"
283 value: shell.orientation
284 }
285 Binding {
286 target: applicationsDisplayLoader.item
287 property: "background"
288 value: shell.background
289 }
290 }
292 Tutorial {
293 id: tutorial
294 objectName: "tutorial"
295 anchors.fill: parent
296 active: AccountsService.demoEdges
297 paused: LightDM.Greeter.active
298 launcher: launcher
299 panel: panel
300 edgeSize: shell.edgeSize
302 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
303 // we'll tell the tutorial to avoid using them on the Desktop. The
304 // Desktop doesn't use the same spread design anyway. The tutorial is
305 // all a bit of a placeholder on non-phone form factors right now.
306 // When the design team gives us more guidance, we can do something
307 // more clever here.
308 // TODO: use DeviceConfiguration instead of checking source
309 useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
311 onFinished: {
312 AccountsService.demoEdges = false;
313 active = false; // for immediate response / if AS is having problems
314 }
315 }
316 }
318 InputMethod {
319 id: inputMethod
320 objectName: "inputMethod"
321 anchors { fill: parent; topMargin: panel.panelHeight }
322 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
323 }
325 Connections {
326 target: SurfaceManager
327 onSurfaceCreated: {
328 if (surface.type == MirSurfaceItem.InputMethod) {
329 inputMethod.surface = surface;
330 }
331 }
333 onSurfaceDestroyed: {
334 if (inputMethod.surface == surface) {
335 inputMethod.surface = null;
336 surface.parent = null;
337 }
338 if (!surface.parent) {
339 // there's no one displaying it. delete it right away
340 surface.release();
341 }
342 }
343 }
344 Connections {
345 target: SessionManager
346 onSessionStopping: {
347 if (!session.parentSession && !session.application) {
348 // nothing is using it. delete it right away
349 session.release();
350 }
351 }
352 }
354 Greeter {
355 id: greeter
356 objectName: "greeter"
358 hides: [launcher, panel.indicators]
359 tabletMode: shell.sideStageEnabled
360 launcherOffset: launcher.progress
361 forcedUnlock: tutorial.running
362 background: shell.background
364 anchors.fill: parent
365 anchors.topMargin: panel.panelHeight
367 // avoid overlapping with Launcher's edge drag area
368 // FIXME: Fix TouchRegistry & friends and remove this workaround
369 // Issue involves launcher's DDA getting disabled on a long
370 // left-edge drag
371 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
373 onSessionStarted: {
374 launcher.hide();
375 }
377 onTease: {
378 if (!tutorial.running) {
379 launcher.tease();
380 }
381 }
383 onEmergencyCall: startLockedApp("dialer-app")
385 Timer {
386 // See powerConnection for why this is useful
387 id: showGreeterDelayed
388 interval: 1
389 onTriggered: {
390 greeter.forceShow();
391 }
392 }
394 Binding {
395 target: ApplicationManager
396 property: "suspended"
397 value: greeter.shown
398 }
399 }
401 Connections {
402 id: callConnection
403 target: callManager
405 onHasCallsChanged: {
406 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
407 // We just received an incoming call while locked. The
408 // indicator will have already launched dialer-app for us, but
409 // there is a race between "hasCalls" changing and the dialer
410 // starting up. So in case we lose that race, we'll start/
411 // focus the dialer ourselves here too. Even if the indicator
412 // didn't launch the dialer for some reason (or maybe a call
413 // started via some other means), if an active call is
414 // happening, we want to be in the dialer.
415 startLockedApp("dialer-app")
416 }
417 }
418 }
420 Connections {
421 id: powerConnection
422 target: Powerd
424 onStatusChanged: {
425 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
426 !callManager.hasCalls && !tutorial.running) {
427 // We don't want to simply call greeter.showNow() here, because
428 // that will take too long. Qt will delay button event
429 // handling until the greeter is done loading and may think the
430 // user held down the power button the whole time, leading to a
431 // power dialog being shown. Instead, delay showing the
432 // greeter until we've finished handling the event. We could
433 // make the greeter load asynchronously instead, but that
434 // introduces a whole host of timing issues, especially with
435 // its animations. So this is simpler.
436 showGreeterDelayed.start();
437 }
438 }
439 }
441 function showHome() {
442 if (tutorial.running) {
443 return
444 }
446 greeter.notifyAboutToFocusApp("unity8-dash");
448 var animate = !LightDM.Greeter.active && !stages.shown
449 dash.setCurrentScope(0, animate, false)
450 ApplicationManager.requestFocusApplication("unity8-dash")
451 }
453 function showDash() {
454 if (greeter.notifyShowingDashFromDrag()) {
455 launcher.fadeOut();
456 }
458 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
459 ApplicationManager.requestFocusApplication("unity8-dash")
460 launcher.fadeOut();
461 }
462 }
464 Item {
465 id: overlay
466 z: 10
468 anchors.fill: parent
470 Panel {
471 id: panel
472 objectName: "panel"
473 anchors.fill: parent //because this draws indicator menus
474 indicators {
475 hides: [launcher]
476 available: tutorial.panelEnabled
477 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
478 && !greeter.hasLockedApp
479 contentEnabled: tutorial.panelContentEnabled
480 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
482 minimizedPanelHeight: units.gu(3)
483 expandedPanelHeight: units.gu(7)
485 indicatorsModel: Indicators.IndicatorsModel {
486 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
487 profile: indicatorProfile
488 Component.onCompleted: load()
489 }
490 }
491 callHint {
492 greeterShown: greeter.shown
493 }
495 property bool topmostApplicationIsFullscreen:
496 ApplicationManager.focusedApplicationId &&
497 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
499 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
500 || greeter.hasLockedApp
501 }
503 Launcher {
504 id: launcher
505 objectName: "launcher"
507 readonly property bool dashSwipe: progress > 0
509 anchors.top: parent.top
510 anchors.topMargin: inverted ? 0 : panel.panelHeight
511 anchors.bottom: parent.bottom
512 width: parent.width
513 dragAreaWidth: shell.edgeSize
514 available: tutorial.launcherEnabled
515 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
516 && !greeter.hasLockedApp
517 inverted: usageModeSettings.usageMode === "Staged"
518 shadeBackground: !tutorial.running
520 onShowDashHome: showHome()
521 onDash: showDash()
522 onDashSwipeChanged: {
523 if (dashSwipe) {
524 dash.setCurrentScope(0, false, true)
525 }
526 }
527 onLauncherApplicationSelected: {
528 if (!tutorial.running) {
529 greeter.notifyAboutToFocusApp(appId);
530 shell.activateApplication(appId)
531 }
532 }
533 onShownChanged: {
534 if (shown) {
535 panel.indicators.hide()
536 }
537 }
538 }
540 Wizard {
541 id: wizard
542 objectName: "wizard"
543 anchors.fill: parent
544 background: shell.background
546 function unlockWhenDoneWithWizard() {
547 if (!active) {
548 Connectivity.unlockAllModems();
549 }
550 }
552 Component.onCompleted: unlockWhenDoneWithWizard()
553 onActiveChanged: unlockWhenDoneWithWizard()
554 }
556 Rectangle {
557 id: modalNotificationBackground
559 visible: notifications.useModal && (notifications.state == "narrow")
560 color: "#000000"
561 anchors.fill: parent
562 opacity: 0.9
564 MouseArea {
565 anchors.fill: parent
566 }
567 }
569 Notifications {
570 id: notifications
572 model: NotificationBackend.Model
573 margin: units.gu(1)
575 y: topmostIsFullscreen ? 0 : panel.panelHeight
576 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
578 states: [
579 State {
580 name: "narrow"
581 when: overlay.width <= units.gu(60)
582 AnchorChanges {
583 target: notifications
584 anchors.left: parent.left
585 anchors.right: parent.right
586 }
587 },
588 State {
589 name: "wide"
590 when: overlay.width > units.gu(60)
591 AnchorChanges {
592 target: notifications
593 anchors.left: undefined
594 anchors.right: parent.right
595 }
596 PropertyChanges { target: notifications; width: units.gu(38) }
597 }
598 ]
599 }
600 }
602 Dialogs {
603 id: dialogs
604 anchors.fill: parent
605 z: overlay.z + 10
606 onPowerOffClicked: {
607 shutdownFadeOutRectangle.enabled = true;
608 shutdownFadeOutRectangle.visible = true;
609 shutdownFadeOut.start();
610 }
611 }
613 Connections {
614 target: SessionBroadcast
615 onShowHome: showHome()
616 }
618 Rectangle {
619 id: shutdownFadeOutRectangle
620 z: screenGrabber.z + 10
621 enabled: false
622 visible: false
623 color: "black"
624 anchors.fill: parent
625 opacity: 0.0
626 NumberAnimation on opacity {
627 id: shutdownFadeOut
628 from: 0.0
629 to: 1.0
630 onStopped: {
631 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
632 DBusUnitySessionService.Shutdown();
633 }
634 }
635 }
636 }