on 7bdef9d help sign in
unity8/qml/Shell.qml
634 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
61 readonly property real panelHeight: panel.panelHeight
63 property bool sideStageEnabled: shell.width >= units.gu(100)
64 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
66 property int orientation
67 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
68 onDeviceOrientationAngleChanged: {
69 if (!OrientationLock.enabled) {
70 orientation = Screen.orientation;
71 }
72 }
73 readonly property bool orientationLockEnabled: OrientationLock.enabled
74 onOrientationLockEnabledChanged: {
75 if (orientationLockEnabled) {
76 OrientationLock.savedOrientation = Screen.orientation;
77 } else {
78 orientation = Screen.orientation;
79 }
80 }
82 // This is _only_ used to expose the property to autopilot tests
83 readonly property string testShellMode: shellMode
85 function activateApplication(appId) {
86 if (ApplicationManager.findApplication(appId)) {
87 ApplicationManager.requestFocusApplication(appId);
88 } else {
89 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
90 ApplicationManager.startApplication(appId, execFlags);
91 }
92 }
94 function startLockedApp(app) {
95 if (greeter.locked) {
96 greeter.lockedApp = app;
97 }
98 shell.activateApplication(app);
99 }
101 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
102 Image {
103 id: asImageTester
104 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
105 height: 0
106 width: 0
107 sourceSize.height: 0
108 sourceSize.width: 0
109 }
111 GSettings {
112 id: 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 Item {
183 id: stages
184 objectName: "stages"
185 width: parent.width
186 height: parent.height
188 Connections {
189 target: ApplicationManager
191 // This signal is also fired when we try to focus the current app
192 // again. We rely on this!
193 onFocusedApplicationIdChanged: {
194 var appId = ApplicationManager.focusedApplicationId;
196 if (tutorial.running && appId != "" && appId != "unity8-dash") {
197 // If this happens on first boot, we may be in edge
198 // tutorial or wizard while receiving a call. But a call
199 // is more important than wizard so just bail out of those.
200 tutorial.finish();
201 wizard.hide();
202 }
204 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
205 // If we are in the middle of a call, make dialer lockedApp and show it.
206 // This can happen if user backs out of dialer back to greeter, then
207 // launches dialer again.
208 greeter.lockedApp = appId;
209 }
210 greeter.notifyAppFocused(appId);
212 panel.indicators.hide();
213 }
215 onApplicationAdded: {
216 launcher.hide();
217 }
218 }
220 Loader {
221 id: applicationsDisplayLoader
222 objectName: "applicationsDisplayLoader"
223 anchors.fill: parent
225 // When we have a locked app, we only want to show that one app.
226 // FIXME: do this in a less traumatic way. We currently only allow
227 // locked apps in phone mode (see FIXME in Lockscreen component in
228 // this same file). When that changes, we need to do something
229 // nicer here. But this code is currently just to prevent a
230 // theoretical attack where user enters lockedApp mode, then makes
231 // the screen larger (maybe connects to monitor) and tries to enter
232 // tablet mode.
233 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
234 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
235 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
237 property bool interactive: tutorial.spreadEnabled
238 && !greeter.shown
239 && panel.indicators.fullyClosed
240 && launcher.progress == 0
241 && !notifications.useModal
243 onInteractiveChanged: { if (interactive) { focus = true; } }
245 Binding {
246 target: applicationsDisplayLoader.item
247 property: "objectName"
248 value: "stage"
249 }
250 Binding {
251 target: applicationsDisplayLoader.item
252 property: "dragAreaWidth"
253 value: shell.edgeSize
254 }
255 Binding {
256 target: applicationsDisplayLoader.item
257 property: "maximizedAppTopMargin"
258 // Not just using panel.panelHeight as that changes depending on the focused app.
259 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
260 }
261 Binding {
262 target: applicationsDisplayLoader.item
263 property: "interactive"
264 value: applicationsDisplayLoader.interactive
265 }
266 Binding {
267 target: applicationsDisplayLoader.item
268 property: "spreadEnabled"
269 value: tutorial.spreadEnabled && !greeter.hasLockedApp
270 }
271 Binding {
272 target: applicationsDisplayLoader.item
273 property: "inverseProgress"
274 value: greeter.locked ? 0 : launcher.progress
275 }
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "orientation"
279 value: shell.orientation
280 }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "background"
284 value: shell.background
285 }
286 }
288 Tutorial {
289 id: tutorial
290 objectName: "tutorial"
291 anchors.fill: parent
292 active: AccountsService.demoEdges
293 paused: LightDM.Greeter.active
294 launcher: launcher
295 panel: panel
296 edgeSize: shell.edgeSize
298 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
299 // we'll tell the tutorial to avoid using them on the Desktop. The
300 // Desktop doesn't use the same spread design anyway. The tutorial is
301 // all a bit of a placeholder on non-phone form factors right now.
302 // When the design team gives us more guidance, we can do something
303 // more clever here.
304 // TODO: use DeviceConfiguration instead of checking source
305 useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
307 onFinished: {
308 AccountsService.demoEdges = false;
309 active = false; // for immediate response / if AS is having problems
310 }
311 }
312 }
314 InputMethod {
315 id: inputMethod
316 objectName: "inputMethod"
317 anchors { fill: parent; topMargin: panel.panelHeight }
318 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
319 }
321 Connections {
322 target: SurfaceManager
323 onSurfaceCreated: {
324 if (surface.type == MirSurfaceItem.InputMethod) {
325 inputMethod.surface = surface;
326 }
327 }
329 onSurfaceDestroyed: {
330 if (inputMethod.surface == surface) {
331 inputMethod.surface = null;
332 surface.parent = null;
333 }
334 if (!surface.parent) {
335 // there's no one displaying it. delete it right away
336 surface.release();
337 }
338 }
339 }
340 Connections {
341 target: SessionManager
342 onSessionStopping: {
343 if (!session.parentSession && !session.application) {
344 // nothing is using it. delete it right away
345 session.release();
346 }
347 }
348 }
350 Greeter {
351 id: greeter
352 objectName: "greeter"
354 hides: [launcher, panel.indicators]
355 tabletMode: shell.sideStageEnabled
356 launcherOffset: launcher.progress
357 forcedUnlock: tutorial.running
358 background: shell.background
360 anchors.fill: parent
361 anchors.topMargin: panel.panelHeight
363 // avoid overlapping with Launcher's edge drag area
364 // FIXME: Fix TouchRegistry & friends and remove this workaround
365 // Issue involves launcher's DDA getting disabled on a long
366 // left-edge drag
367 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
369 onSessionStarted: {
370 launcher.hide();
371 }
373 onTease: {
374 if (!tutorial.running) {
375 launcher.tease();
376 }
377 }
379 onEmergencyCall: startLockedApp("dialer-app")
381 Timer {
382 // See powerConnection for why this is useful
383 id: showGreeterDelayed
384 interval: 1
385 onTriggered: {
386 greeter.forceShow();
387 }
388 }
390 Binding {
391 target: ApplicationManager
392 property: "suspended"
393 value: greeter.shown
394 }
395 }
397 Connections {
398 id: callConnection
399 target: callManager
401 onHasCallsChanged: {
402 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
403 // We just received an incoming call while locked. The
404 // indicator will have already launched dialer-app for us, but
405 // there is a race between "hasCalls" changing and the dialer
406 // starting up. So in case we lose that race, we'll start/
407 // focus the dialer ourselves here too. Even if the indicator
408 // didn't launch the dialer for some reason (or maybe a call
409 // started via some other means), if an active call is
410 // happening, we want to be in the dialer.
411 startLockedApp("dialer-app")
412 }
413 }
414 }
416 Connections {
417 id: powerConnection
418 target: Powerd
420 onStatusChanged: {
421 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
422 !callManager.hasCalls && !tutorial.running) {
423 // We don't want to simply call greeter.showNow() here, because
424 // that will take too long. Qt will delay button event
425 // handling until the greeter is done loading and may think the
426 // user held down the power button the whole time, leading to a
427 // power dialog being shown. Instead, delay showing the
428 // greeter until we've finished handling the event. We could
429 // make the greeter load asynchronously instead, but that
430 // introduces a whole host of timing issues, especially with
431 // its animations. So this is simpler.
432 showGreeterDelayed.start();
433 }
434 }
435 }
437 function showHome() {
438 if (tutorial.running) {
439 return
440 }
442 greeter.notifyAboutToFocusApp("unity8-dash");
444 var animate = !LightDM.Greeter.active && !stages.shown
445 dash.setCurrentScope(0, animate, false)
446 ApplicationManager.requestFocusApplication("unity8-dash")
447 }
449 function showDash() {
450 if (greeter.notifyShowingDashFromDrag()) {
451 launcher.fadeOut();
452 }
454 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
455 ApplicationManager.requestFocusApplication("unity8-dash")
456 launcher.fadeOut();
457 }
458 }
460 Item {
461 id: overlay
462 z: 10
464 anchors.fill: parent
466 Panel {
467 id: panel
468 objectName: "panel"
469 anchors.fill: parent //because this draws indicator menus
470 indicators {
471 hides: [launcher]
472 available: tutorial.panelEnabled
473 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
474 && !greeter.hasLockedApp
475 contentEnabled: tutorial.panelContentEnabled
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
478 minimizedPanelHeight: units.gu(3)
479 expandedPanelHeight: units.gu(7)
481 indicatorsModel: Indicators.IndicatorsModel {
482 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
483 profile: indicatorProfile
484 Component.onCompleted: load()
485 }
486 }
487 callHint {
488 greeterShown: greeter.shown
489 }
491 property bool topmostApplicationIsFullscreen:
492 ApplicationManager.focusedApplicationId &&
493 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
495 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
496 || greeter.hasLockedApp
497 }
499 Launcher {
500 id: launcher
501 objectName: "launcher"
503 readonly property bool dashSwipe: progress > 0
505 anchors.top: parent.top
506 anchors.topMargin: inverted ? 0 : panel.panelHeight
507 anchors.bottom: parent.bottom
508 width: parent.width
509 dragAreaWidth: shell.edgeSize
510 available: tutorial.launcherEnabled
511 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
512 && !greeter.hasLockedApp
513 inverted: usageModeSettings.usageMode === "Staged"
514 shadeBackground: !tutorial.running
516 onShowDashHome: showHome()
517 onDash: showDash()
518 onDashSwipeChanged: {
519 if (dashSwipe) {
520 dash.setCurrentScope(0, false, true)
521 }
522 }
523 onLauncherApplicationSelected: {
524 if (!tutorial.running) {
525 greeter.notifyAboutToFocusApp(appId);
526 shell.activateApplication(appId)
527 }
528 }
529 onShownChanged: {
530 if (shown) {
531 panel.indicators.hide()
532 }
533 }
534 }
536 Wizard {
537 id: wizard
538 objectName: "wizard"
539 anchors.fill: parent
540 background: shell.background
542 function unlockWhenDoneWithWizard() {
543 if (!active) {
544 Connectivity.unlockAllModems();
545 }
546 }
548 Component.onCompleted: unlockWhenDoneWithWizard()
549 onActiveChanged: unlockWhenDoneWithWizard()
550 }
552 Rectangle {
553 id: modalNotificationBackground
555 visible: notifications.useModal && (notifications.state == "narrow")
556 color: "#000000"
557 anchors.fill: parent
558 opacity: 0.9
560 MouseArea {
561 anchors.fill: parent
562 }
563 }
565 Notifications {
566 id: notifications
568 model: NotificationBackend.Model
569 margin: units.gu(1)
571 y: topmostIsFullscreen ? 0 : panel.panelHeight
572 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
574 states: [
575 State {
576 name: "narrow"
577 when: overlay.width <= units.gu(60)
578 AnchorChanges {
579 target: notifications
580 anchors.left: parent.left
581 anchors.right: parent.right
582 }
583 },
584 State {
585 name: "wide"
586 when: overlay.width > units.gu(60)
587 AnchorChanges {
588 target: notifications
589 anchors.left: undefined
590 anchors.right: parent.right
591 }
592 PropertyChanges { target: notifications; width: units.gu(38) }
593 }
594 ]
595 }
596 }
598 Dialogs {
599 id: dialogs
600 anchors.fill: parent
601 z: overlay.z + 10
602 onPowerOffClicked: {
603 shutdownFadeOutRectangle.enabled = true;
604 shutdownFadeOutRectangle.visible = true;
605 shutdownFadeOut.start();
606 }
607 }
609 Connections {
610 target: SessionBroadcast
611 onShowHome: showHome()
612 }
614 Rectangle {
615 id: shutdownFadeOutRectangle
616 z: screenGrabber.z + 10
617 enabled: false
618 visible: false
619 color: "black"
620 anchors.fill: parent
621 opacity: 0.0
622 NumberAnimation on opacity {
623 id: shutdownFadeOut
624 from: 0.0
625 to: 1.0
626 onStopped: {
627 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
628 DBusUnitySessionService.Shutdown();
629 }
630 }
631 }
632 }