on eae7049 help sign in
unity8/qml/Shell.qml
714 lines 25 kB history raw
1/*
2 * Copyright (C) 2013-2015 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 GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
29import Utils 0.1
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
44import Cursor 1.0
47Item {
48 id: shell
50 // to be set from outside
51 property int orientationAngle: 0
52 property int orientation
53 property int primaryOrientation
54 property int nativeOrientation
55 property real nativeWidth
56 property real nativeHeight
57 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
58 property bool beingResized
59 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
60 property string mode: "full-greeter"
61 function updateFocusedAppOrientation() {
62 applicationsDisplayLoader.item.updateFocusedAppOrientation();
63 }
64 function updateFocusedAppOrientationAnimated() {
65 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
66 }
67 property bool hasMouse: false
69 // to be read from outside
70 readonly property int mainAppWindowOrientationAngle:
71 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
73 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
74 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
75 && (!greeter || !greeter.animating)
77 readonly property bool showingGreeter: greeter && greeter.shown
79 property bool startingUp: true
80 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
82 property int supportedOrientations: {
83 if (startingUp) {
84 // Ensure we don't rotate during start up
85 return Qt.PrimaryOrientation;
86 } else if (greeter && greeter.shown) {
87 return Qt.PrimaryOrientation;
88 } else if (mainApp) {
89 return mainApp.supportedOrientations;
90 } else {
91 // we just don't care
92 return Qt.PortraitOrientation
93 | Qt.LandscapeOrientation
94 | Qt.InvertedPortraitOrientation
95 | Qt.InvertedLandscapeOrientation;
96 }
97 }
99 // For autopilot consumption
100 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
102 // internal props from here onwards
103 readonly property var mainApp:
104 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
106 // Disable everything while greeter is waiting, so that the user can't swipe
107 // the greeter or launcher until we know whether the session is locked.
108 enabled: greeter && !greeter.waiting
110 property real edgeSize: units.gu(2)
111 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
112 property url background: asImageTester.status == Image.Ready ? asImageTester.source
113 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
115 readonly property alias greeter: greeterLoader.item
117 function activateApplication(appId) {
118 if (ApplicationManager.findApplication(appId)) {
119 ApplicationManager.requestFocusApplication(appId);
120 } else {
121 var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
122 : ApplicationManager.NoFlag;
123 ApplicationManager.startApplication(appId, execFlags);
124 }
125 }
127 function startLockedApp(app) {
128 if (greeter.locked) {
129 greeter.lockedApp = app;
130 }
131 shell.activateApplication(app);
132 }
134 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
135 Image {
136 id: asImageTester
137 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
138 height: 0
139 width: 0
140 sourceSize.height: 0
141 sourceSize.width: 0
142 }
144 GSettings {
145 id: backgroundSettings
146 schema.id: "org.gnome.desktop.background"
147 }
149 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
150 Image {
151 id: gsImageTester
152 source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
153 height: 0
154 width: 0
155 sourceSize.height: 0
156 sourceSize.width: 0
157 }
159 Binding {
160 target: LauncherModel
161 property: "applicationManager"
162 value: ApplicationManager
163 }
165 Component.onCompleted: {
166 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
167 if (ApplicationManager.count > 0) {
168 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
169 }
170 finishStartUpTimer.start();
171 }
173 LightDM{id: lightDM} // Provide backend access
174 VolumeControl {
175 id: volumeControl
176 indicators: panel.indicators
177 }
179 DashCommunicator {
180 id: dash
181 objectName: "dashCommunicator"
182 }
184 PhysicalKeysMapper {
185 id: physicalKeysMapper
186 objectName: "physicalKeysMapper"
188 onPowerKeyLongPressed: dialogs.showPowerDialog();
189 onVolumeDownTriggered: volumeControl.volumeDown();
190 onVolumeUpTriggered: volumeControl.volumeUp();
191 onScreenshotTriggered: screenGrabber.capture();
192 }
194 GlobalShortcut {
195 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
196 }
198 WindowKeysFilter {
199 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
200 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
201 }
203 HomeKeyWatcher {
204 onActivated: { launcher.fadeOut(); shell.showHome(); }
205 }
207 Item {
208 id: stages
209 objectName: "stages"
210 width: parent.width
211 height: parent.height
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 != "" && 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 }
240 onApplicationAdded: {
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.
259 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
260 ? "phone"
261 : shell.usageScenario
262 source: {
263 if(shell.mode === "greeter") {
264 return "Stages/ShimStage.qml"
265 } else if (applicationsDisplayLoader.usageScenario === "phone") {
266 return "Stages/PhoneStage.qml";
267 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
268 return "Stages/TabletStage.qml";
269 } else {
270 return "Stages/DesktopStage.qml";
271 }
272 }
274 property bool interactive: tutorial.spreadEnabled
275 && (!greeter || !greeter.shown)
276 && panel.indicators.fullyClosed
277 && launcher.progress == 0
278 && !notifications.useModal
280 onInteractiveChanged: { if (interactive) { focus = true; } }
282 Binding {
283 target: applicationsDisplayLoader.item
284 property: "objectName"
285 value: "stage"
286 }
287 Binding {
288 target: applicationsDisplayLoader.item
289 property: "dragAreaWidth"
290 value: shell.edgeSize
291 }
292 Binding {
293 target: applicationsDisplayLoader.item
294 property: "maximizedAppTopMargin"
295 // Not just using panel.panelHeight as that changes depending on the focused app.
296 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "interactive"
301 value: applicationsDisplayLoader.interactive
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "spreadEnabled"
306 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
307 }
308 Binding {
309 target: applicationsDisplayLoader.item
310 property: "inverseProgress"
311 value: greeter && greeter.locked ? 0 : launcher.progress
312 }
313 Binding {
314 target: applicationsDisplayLoader.item
315 property: "shellOrientationAngle"
316 value: shell.orientationAngle
317 }
318 Binding {
319 target: applicationsDisplayLoader.item
320 property: "shellOrientation"
321 value: shell.orientation
322 }
323 Binding {
324 target: applicationsDisplayLoader.item
325 property: "background"
326 value: shell.background
327 }
328 Binding {
329 target: applicationsDisplayLoader.item
330 property: "shellPrimaryOrientation"
331 value: shell.primaryOrientation
332 }
333 Binding {
334 target: applicationsDisplayLoader.item
335 property: "nativeOrientation"
336 value: shell.nativeOrientation
337 }
338 Binding {
339 target: applicationsDisplayLoader.item
340 property: "nativeWidth"
341 value: shell.nativeWidth
342 }
343 Binding {
344 target: applicationsDisplayLoader.item
345 property: "nativeHeight"
346 value: shell.nativeHeight
347 }
348 Binding {
349 target: applicationsDisplayLoader.item
350 property: "beingResized"
351 value: shell.beingResized
352 }
353 Binding {
354 target: applicationsDisplayLoader.item
355 property: "keepDashRunning"
356 value: launcher.shown || launcher.dashSwipe
357 }
358 Binding {
359 target: applicationsDisplayLoader.item
360 property: "suspended"
361 value: greeter.shown
362 }
363 Binding {
364 target: applicationsDisplayLoader.item
365 property: "altTabPressed"
366 value: physicalKeysMapper.altTabPressed
367 }
368 }
370 Tutorial {
371 id: tutorial
372 objectName: "tutorial"
373 anchors.fill: parent
375 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
376 // we skip the tutorial on the Desktop to avoid using them. The
377 // Desktop doesn't use the same spread design anyway. The tutorial is
378 // all a bit of a placeholder on non-phone form factors right now.
379 // When the design team gives us more guidance, we can do something
380 // more clever here.
381 active: usageScenario != "desktop" && AccountsService.demoEdges
383 paused: lightDM.greeter.active
384 launcher: launcher
385 panel: panel
386 edgeSize: shell.edgeSize
388 onFinished: {
389 AccountsService.demoEdges = false;
390 active = false; // for immediate response / if AS is having problems
391 }
392 }
393 }
395 InputMethod {
396 id: inputMethod
397 objectName: "inputMethod"
398 anchors { fill: parent; topMargin: panel.panelHeight }
399 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
400 }
402 Connections {
403 target: SessionManager
404 onSessionStopping: {
405 if (!session.parentSession && !session.application) {
406 // nothing is using it. delete it right away
407 session.release();
408 }
409 }
410 }
412 Loader {
413 id: greeterLoader
414 anchors.fill: parent
415 anchors.topMargin: panel.panelHeight
416 sourceComponent: shell.mode != "shell" ? integratedGreeter :
417 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
418 onLoaded: {
419 item.objectName = "greeter"
420 }
421 }
423 Component {
424 id: integratedGreeter
425 Greeter {
427 hides: [launcher, panel.indicators]
428 tabletMode: shell.usageScenario != "phone"
429 launcherOffset: launcher.progress
430 forcedUnlock: tutorial.running
431 background: shell.background
433 // avoid overlapping with Launcher's edge drag area
434 // FIXME: Fix TouchRegistry & friends and remove this workaround
435 // Issue involves launcher's DDA getting disabled on a long
436 // left-edge drag
437 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
439 onSessionStarted: {
440 launcher.hide();
441 }
443 onTease: {
444 if (!tutorial.running) {
445 launcher.tease();
446 }
447 }
449 onEmergencyCall: startLockedApp("dialer-app")
450 }
451 }
453 Timer {
454 // See powerConnection for why this is useful
455 id: showGreeterDelayed
456 interval: 1
457 onTriggered: {
458 greeter.forceShow();
459 }
460 }
462 Connections {
463 id: callConnection
464 target: callManager
466 onHasCallsChanged: {
467 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
468 // We just received an incoming call while locked. The
469 // indicator will have already launched dialer-app for us, but
470 // there is a race between "hasCalls" changing and the dialer
471 // starting up. So in case we lose that race, we'll start/
472 // focus the dialer ourselves here too. Even if the indicator
473 // didn't launch the dialer for some reason (or maybe a call
474 // started via some other means), if an active call is
475 // happening, we want to be in the dialer.
476 startLockedApp("dialer-app")
477 }
478 }
479 }
481 Connections {
482 id: powerConnection
483 target: Powerd
485 onStatusChanged: {
486 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
487 !callManager.hasCalls && !tutorial.running) {
488 // We don't want to simply call greeter.showNow() here, because
489 // that will take too long. Qt will delay button event
490 // handling until the greeter is done loading and may think the
491 // user held down the power button the whole time, leading to a
492 // power dialog being shown. Instead, delay showing the
493 // greeter until we've finished handling the event. We could
494 // make the greeter load asynchronously instead, but that
495 // introduces a whole host of timing issues, especially with
496 // its animations. So this is simpler.
497 showGreeterDelayed.start();
498 }
499 }
500 }
502 function showHome() {
503 if (tutorial.running) {
504 return
505 }
507 greeter.notifyAboutToFocusApp("unity8-dash");
509 var animate = !lightDM.greeter.active && !stages.shown
510 dash.setCurrentScope(0, animate, false)
511 ApplicationManager.requestFocusApplication("unity8-dash")
512 }
514 function showDash() {
515 if (greeter.notifyShowingDashFromDrag()) {
516 launcher.fadeOut();
517 }
519 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
520 ApplicationManager.requestFocusApplication("unity8-dash")
521 launcher.fadeOut();
522 }
523 }
525 Item {
526 id: overlay
527 z: 10
529 anchors.fill: parent
531 Panel {
532 id: panel
533 objectName: "panel"
534 anchors.fill: parent //because this draws indicator menus
535 indicators {
536 hides: [launcher]
537 available: tutorial.panelEnabled
538 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
539 && (!greeter || !greeter.hasLockedApp)
540 contentEnabled: tutorial.panelContentEnabled
541 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
543 minimizedPanelHeight: units.gu(3)
544 expandedPanelHeight: units.gu(7)
546 indicatorsModel: Indicators.IndicatorsModel {
547 // tablet and phone both use the same profile
548 profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
549 Component.onCompleted: load();
550 }
551 }
553 callHint {
554 greeterShown: greeter.shown
555 }
557 property bool topmostApplicationIsFullscreen:
558 ApplicationManager.focusedApplicationId &&
559 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
561 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
562 || greeter.hasLockedApp
563 }
565 Launcher {
566 id: launcher
567 objectName: "launcher"
569 readonly property bool dashSwipe: progress > 0
571 anchors.top: parent.top
572 anchors.topMargin: inverted ? 0 : panel.panelHeight
573 anchors.bottom: parent.bottom
574 width: parent.width
575 dragAreaWidth: shell.edgeSize
576 available: tutorial.launcherEnabled
577 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
578 && !greeter.hasLockedApp
579 inverted: shell.usageScenario !== "desktop"
580 shadeBackground: !tutorial.running
582 onShowDashHome: showHome()
583 onDash: showDash()
584 onDashSwipeChanged: {
585 if (dashSwipe) {
586 dash.setCurrentScope(0, false, true)
587 }
588 }
589 onLauncherApplicationSelected: {
590 if (!tutorial.running) {
591 greeter.notifyAboutToFocusApp(appId);
592 shell.activateApplication(appId)
593 }
594 }
595 onShownChanged: {
596 if (shown) {
597 panel.indicators.hide()
598 }
599 }
600 }
602 Wizard {
603 id: wizard
604 objectName: "wizard"
605 anchors.fill: parent
606 background: shell.background
608 function unlockWhenDoneWithWizard() {
609 if (!active) {
610 Connectivity.unlockAllModems();
611 }
612 }
614 Component.onCompleted: unlockWhenDoneWithWizard()
615 onActiveChanged: unlockWhenDoneWithWizard()
616 }
618 Rectangle {
619 id: modalNotificationBackground
621 visible: notifications.useModal
622 color: "#000000"
623 anchors.fill: parent
624 opacity: 0.9
626 MouseArea {
627 anchors.fill: parent
628 }
629 }
631 Notifications {
632 id: notifications
634 model: NotificationBackend.Model
635 margin: units.gu(1)
636 hasMouse: shell.hasMouse
638 y: topmostIsFullscreen ? 0 : panel.panelHeight
639 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
641 states: [
642 State {
643 name: "narrow"
644 when: overlay.width <= units.gu(60)
645 AnchorChanges {
646 target: notifications
647 anchors.left: parent.left
648 anchors.right: parent.right
649 }
650 },
651 State {
652 name: "wide"
653 when: overlay.width > units.gu(60)
654 AnchorChanges {
655 target: notifications
656 anchors.left: undefined
657 anchors.right: parent.right
658 }
659 PropertyChanges { target: notifications; width: units.gu(38) }
660 }
661 ]
662 }
663 }
665 Dialogs {
666 id: dialogs
667 objectName: "dialogs"
668 anchors.fill: parent
669 z: overlay.z + 10
670 usageScenario: shell.usageScenario
671 onPowerOffClicked: {
672 shutdownFadeOutRectangle.enabled = true;
673 shutdownFadeOutRectangle.visible = true;
674 shutdownFadeOut.start();
675 }
676 }
678 Connections {
679 target: SessionBroadcast
680 onShowHome: showHome()
681 }
683 ScreenGrabber {
684 id: screenGrabber
685 z: dialogs.z + 10
686 }
688 Cursor {
689 id: cursor
690 visible: shell.hasMouse
691 z: screenGrabber.z + 1
692 }
694 Rectangle {
695 id: shutdownFadeOutRectangle
696 z: cursor.z + 1
697 enabled: false
698 visible: false
699 color: "black"
700 anchors.fill: parent
701 opacity: 0.0
702 NumberAnimation on opacity {
703 id: shutdownFadeOut
704 from: 0.0
705 to: 1.0
706 onStopped: {
707 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
708 DBusUnitySessionService.shutdown();
709 }
710 }
711 }
712 }