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