on 8755192 help sign in
unity8/qml/Shell.qml
759 lines 26 kB history raw
1/*
2 * Copyright (C) 2013-2016 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.4
18import QtQuick.Window 2.2
19import AccountsService 0.1
20import Unity.Application 0.1
21import Ubuntu.Components 1.3
22import Ubuntu.Components.Popups 1.3
23import Ubuntu.Gestures 0.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Connectivity 0.1
26import Unity.Launcher 0.1
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
28import GSettings 1.0
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 Orientations orientations
54 property real nativeWidth
55 property real nativeHeight
56 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
57 property bool beingResized
58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59 property string mode: "full-greeter"
60 property alias oskEnabled: inputMethod.enabled
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 (applicationsDisplayLoader.item) {
89 return shell.orientations.map(applicationsDisplayLoader.item.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)
112 WallpaperResolver {
113 id: wallpaperResolver
114 width: shell.width
115 }
117 readonly property alias greeter: greeterLoader.item
119 function activateApplication(appId) {
120 if (ApplicationManager.findApplication(appId)) {
121 ApplicationManager.requestFocusApplication(appId);
122 } else {
123 var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
124 : ApplicationManager.NoFlag;
125 ApplicationManager.startApplication(appId, execFlags);
126 }
127 }
129 function startLockedApp(app) {
130 if (greeter.locked) {
131 greeter.lockedApp = app;
132 }
133 shell.activateApplication(app);
134 }
136 Binding {
137 target: LauncherModel
138 property: "applicationManager"
139 value: ApplicationManager
140 }
142 Component.onCompleted: {
143 theme.name = "Ubuntu.Components.Themes.SuruDark"
144 if (ApplicationManager.count > 0) {
145 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
146 }
147 finishStartUpTimer.start();
148 }
150 VolumeControl {
151 id: volumeControl
152 indicators: panel.indicators
153 }
155 DashCommunicator {
156 id: dash
157 objectName: "dashCommunicator"
158 }
160 PhysicalKeysMapper {
161 id: physicalKeysMapper
162 objectName: "physicalKeysMapper"
164 onPowerKeyLongPressed: dialogs.showPowerDialog();
165 onVolumeDownTriggered: volumeControl.volumeDown();
166 onVolumeUpTriggered: volumeControl.volumeUp();
167 onScreenshotTriggered: screenGrabber.capture();
168 }
170 GlobalShortcut {
171 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
172 }
174 WindowKeysFilter {
175 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, currentEventTimestamp);
176 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
177 }
179 WindowInputMonitor {
180 onHomeKeyActivated: { launcher.fadeOut(); shell.showHome(); }
181 onTouchBegun: { cursor.opacity = 0; }
182 onTouchEnded: {
183 // move the (hidden) cursor to the last known touch position
184 var mappedCoords = mapFromItem(null, pos.x, pos.y);
185 cursor.x = mappedCoords.x;
186 cursor.y = mappedCoords.y;
187 }
188 }
190 GSettings {
191 id: settings
192 schema.id: "com.canonical.Unity8"
193 }
195 Item {
196 id: stages
197 objectName: "stages"
198 width: parent.width
199 height: parent.height
201 Connections {
202 target: ApplicationManager
204 // This signal is also fired when we try to focus the current app
205 // again. We rely on this!
206 onFocusedApplicationIdChanged: {
207 var appId = ApplicationManager.focusedApplicationId;
209 if (tutorial.running && appId != "" && appId != "unity8-dash") {
210 // If this happens on first boot, we may be in edge
211 // tutorial or wizard while receiving a call. But a call
212 // is more important than wizard so just bail out of those.
213 tutorial.finish();
214 wizard.hide();
215 }
217 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
218 // If we are in the middle of a call, make dialer lockedApp and show it.
219 // This can happen if user backs out of dialer back to greeter, then
220 // launches dialer again.
221 greeter.lockedApp = appId;
222 }
223 greeter.notifyAppFocused(appId);
225 panel.indicators.hide();
226 }
228 onApplicationAdded: {
229 launcher.hide();
230 }
231 }
233 Loader {
234 id: applicationsDisplayLoader
235 objectName: "applicationsDisplayLoader"
236 anchors.fill: parent
238 // When we have a locked app, we only want to show that one app.
239 // FIXME: do this in a less traumatic way. We currently only allow
240 // locked apps in phone mode (see FIXME in Lockscreen component in
241 // this same file). When that changes, we need to do something
242 // nicer here. But this code is currently just to prevent a
243 // theoretical attack where user enters lockedApp mode, then makes
244 // the screen larger (maybe connects to monitor) and tries to enter
245 // tablet mode.
247 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
248 ? "phone"
249 : shell.usageScenario
250 source: {
251 if(shell.mode === "greeter") {
252 return "Stages/ShimStage.qml"
253 } else if (applicationsDisplayLoader.usageScenario === "phone") {
254 return "Stages/PhoneStage.qml";
255 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
256 return "Stages/TabletStage.qml";
257 } else {
258 return "Stages/DesktopStage.qml";
259 }
260 }
262 property bool interactive: tutorial.spreadEnabled
263 && (!greeter || !greeter.shown)
264 && panel.indicators.fullyClosed
265 && launcher.progress == 0
266 && !notifications.useModal
268 onInteractiveChanged: { if (interactive) { focus = true; } }
270 Binding {
271 target: applicationsDisplayLoader.item
272 property: "objectName"
273 value: "stage"
274 }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "dragAreaWidth"
278 value: shell.edgeSize
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "maximizedAppTopMargin"
283 // Not just using panel.panelHeight as that changes depending on the focused app.
284 value: panel.indicators.minimizedPanelHeight
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "interactive"
289 value: applicationsDisplayLoader.interactive
290 }
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "spreadEnabled"
294 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "inverseProgress"
299 value: greeter && greeter.locked ? 0 : launcher.progress
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "shellOrientationAngle"
304 value: shell.orientationAngle
305 }
306 Binding {
307 target: applicationsDisplayLoader.item
308 property: "shellOrientation"
309 value: shell.orientation
310 }
311 Binding {
312 target: applicationsDisplayLoader.item
313 property: "orientations"
314 value: shell.orientations
315 }
316 Binding {
317 target: applicationsDisplayLoader.item
318 property: "background"
319 value: wallpaperResolver.background
320 }
321 Binding {
322 target: applicationsDisplayLoader.item
323 property: "nativeWidth"
324 value: shell.nativeWidth
325 }
326 Binding {
327 target: applicationsDisplayLoader.item
328 property: "nativeHeight"
329 value: shell.nativeHeight
330 }
331 Binding {
332 target: applicationsDisplayLoader.item
333 property: "beingResized"
334 value: shell.beingResized
335 }
336 Binding {
337 target: applicationsDisplayLoader.item
338 property: "keepDashRunning"
339 value: launcher.shown || launcher.dashSwipe
340 }
341 Binding {
342 target: applicationsDisplayLoader.item
343 property: "suspended"
344 value: greeter.shown
345 }
346 Binding {
347 target: applicationsDisplayLoader.item
348 property: "altTabPressed"
349 value: physicalKeysMapper.altTabPressed
350 }
351 Binding {
352 target: applicationsDisplayLoader.item
353 property: "leftMargin"
354 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
355 }
356 }
358 Tutorial {
359 id: tutorial
360 objectName: "tutorial"
361 anchors.fill: parent
363 // EdgeDragAreas don't work with mice. So to avoid trapping the user,
364 // we skip the tutorial on the Desktop to avoid using them. The
365 // Desktop doesn't use the same spread design anyway. The tutorial is
366 // all a bit of a placeholder on non-phone form factors right now.
367 // When the design team gives us more guidance, we can do something
368 // more clever here.
369 active: usageScenario != "desktop" && AccountsService.demoEdges
371 paused: LightDMService.greeter.active
372 launcher: launcher
373 panel: panel
374 edgeSize: shell.edgeSize
376 onFinished: {
377 AccountsService.demoEdges = false;
378 active = false; // for immediate response / if AS is having problems
379 }
380 }
381 }
383 InputMethod {
384 id: inputMethod
385 objectName: "inputMethod"
386 anchors {
387 fill: parent
388 topMargin: panel.panelHeight
389 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
390 }
391 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
392 }
394 Connections {
395 target: SessionManager
396 onSessionStopping: {
397 if (!session.parentSession && !session.application) {
398 // nothing is using it. delete it right away
399 session.release();
400 }
401 }
402 }
404 Loader {
405 id: greeterLoader
406 anchors.fill: parent
407 anchors.topMargin: panel.panelHeight
408 sourceComponent: shell.mode != "shell" ? integratedGreeter :
409 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
410 onLoaded: {
411 item.objectName = "greeter"
412 }
413 }
415 Component {
416 id: integratedGreeter
417 Greeter {
419 hides: [launcher, panel.indicators]
420 tabletMode: shell.usageScenario != "phone"
421 launcherOffset: launcher.progress
422 forcedUnlock: tutorial.running
423 background: wallpaperResolver.background
425 // avoid overlapping with Launcher's edge drag area
426 // FIXME: Fix TouchRegistry & friends and remove this workaround
427 // Issue involves launcher's DDA getting disabled on a long
428 // left-edge drag
429 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
431 onSessionStarted: {
432 launcher.hide();
433 }
435 onTease: {
436 if (!tutorial.running) {
437 launcher.tease();
438 }
439 }
441 onEmergencyCall: startLockedApp("dialer-app")
442 }
443 }
445 Timer {
446 // See powerConnection for why this is useful
447 id: showGreeterDelayed
448 interval: 1
449 onTriggered: {
450 greeter.forceShow();
451 }
452 }
454 Connections {
455 id: callConnection
456 target: callManager
458 onHasCallsChanged: {
459 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
460 // We just received an incoming call while locked. The
461 // indicator will have already launched dialer-app for us, but
462 // there is a race between "hasCalls" changing and the dialer
463 // starting up. So in case we lose that race, we'll start/
464 // focus the dialer ourselves here too. Even if the indicator
465 // didn't launch the dialer for some reason (or maybe a call
466 // started via some other means), if an active call is
467 // happening, we want to be in the dialer.
468 startLockedApp("dialer-app")
469 }
470 }
471 }
473 Connections {
474 id: powerConnection
475 target: Powerd
477 onStatusChanged: {
478 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
479 !callManager.hasCalls && !tutorial.running) {
480 // We don't want to simply call greeter.showNow() here, because
481 // that will take too long. Qt will delay button event
482 // handling until the greeter is done loading and may think the
483 // user held down the power button the whole time, leading to a
484 // power dialog being shown. Instead, delay showing the
485 // greeter until we've finished handling the event. We could
486 // make the greeter load asynchronously instead, but that
487 // introduces a whole host of timing issues, especially with
488 // its animations. So this is simpler.
489 showGreeterDelayed.start();
490 }
491 }
492 }
494 function showHome() {
495 if (tutorial.running) {
496 return
497 }
499 greeter.notifyAboutToFocusApp("unity8-dash");
501 var animate = !LightDMService.greeter.active && !stages.shown
502 dash.setCurrentScope(0, animate, false)
503 ApplicationManager.requestFocusApplication("unity8-dash")
504 }
506 function showDash() {
507 if (greeter.notifyShowingDashFromDrag()) {
508 launcher.fadeOut();
509 }
511 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
512 ApplicationManager.requestFocusApplication("unity8-dash")
513 launcher.fadeOut();
514 }
515 }
517 Item {
518 id: overlay
519 z: 10
521 anchors.fill: parent
523 Panel {
524 id: panel
525 objectName: "panel"
526 anchors.fill: parent //because this draws indicator menus
527 indicators {
528 hides: [launcher]
529 available: tutorial.panelEnabled
530 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
531 && (!greeter || !greeter.hasLockedApp)
532 contentEnabled: tutorial.panelContentEnabled
533 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
535 minimizedPanelHeight: units.gu(3)
536 expandedPanelHeight: units.gu(7)
538 indicatorsModel: Indicators.IndicatorsModel {
539 // tablet and phone both use the same profile
540 profile: "phone"
541 Component.onCompleted: load();
542 }
543 }
545 callHint {
546 greeterShown: greeter.shown
547 }
549 readonly property bool topmostApplicationIsFullscreen:
550 ApplicationManager.focusedApplicationId &&
551 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
553 fullscreenMode: (topmostApplicationIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
554 || greeter.hasLockedApp
555 locked: greeter && greeter.active
556 }
558 Launcher {
559 id: launcher
560 objectName: "launcher"
562 readonly property bool dashSwipe: progress > 0
564 anchors.top: parent.top
565 anchors.topMargin: inverted ? 0 : panel.panelHeight
566 anchors.bottom: parent.bottom
567 width: parent.width
568 dragAreaWidth: shell.edgeSize
569 available: tutorial.launcherEnabled
570 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
571 && !greeter.hasLockedApp
572 inverted: shell.usageScenario !== "desktop"
573 shadeBackground: !tutorial.running
574 superPressed: physicalKeysMapper.superPressed
575 superTabPressed: physicalKeysMapper.superTabPressed
576 panelWidth: units.gu(settings.launcherWidth)
577 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
579 onShowDashHome: showHome()
580 onDash: showDash()
581 onDashSwipeChanged: {
582 if (dashSwipe) {
583 dash.setCurrentScope(0, false, true)
584 }
585 }
586 onLauncherApplicationSelected: {
587 if (!tutorial.running) {
588 greeter.notifyAboutToFocusApp(appId);
589 shell.activateApplication(appId)
590 }
591 }
592 onShownChanged: {
593 if (shown) {
594 panel.indicators.hide()
595 }
596 }
597 onFocusChanged: {
598 if (!focus) {
599 applicationsDisplayLoader.focus = true;
600 }
601 }
603 GlobalShortcut {
604 shortcut: Qt.AltModifier | Qt.Key_F1
605 onTriggered: {
606 launcher.openForKeyboardNavigation();
607 }
608 }
609 GlobalShortcut {
610 shortcut: Qt.MetaModifier | Qt.Key_0
611 onTriggered: {
612 if (LauncherModel.get(9)) {
613 activateApplication(LauncherModel.get(9).appId);
614 }
615 }
616 }
617 Repeater {
618 model: 9
619 GlobalShortcut {
620 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
621 onTriggered: {
622 if (LauncherModel.get(index)) {
623 activateApplication(LauncherModel.get(index).appId);
624 }
625 }
626 }
627 }
628 }
630 Wizard {
631 id: wizard
632 objectName: "wizard"
633 anchors.fill: parent
634 deferred: shell.mode === "greeter"
635 background: wallpaperResolver.background
637 function unlockWhenDoneWithWizard() {
638 if (!active) {
639 Connectivity.unlockAllModems();
640 }
641 }
643 Component.onCompleted: unlockWhenDoneWithWizard()
644 onActiveChanged: unlockWhenDoneWithWizard()
645 }
647 Rectangle {
648 id: modalNotificationBackground
650 visible: notifications.useModal
651 color: "#000000"
652 anchors.fill: parent
653 opacity: 0.9
655 MouseArea {
656 anchors.fill: parent
657 }
658 }
660 Notifications {
661 id: notifications
663 model: NotificationBackend.Model
664 margin: units.gu(1)
665 hasMouse: shell.hasMouse
666 background: wallpaperResolver.background
668 y: topmostIsFullscreen ? 0 : panel.panelHeight
669 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
671 states: [
672 State {
673 name: "narrow"
674 when: overlay.width <= units.gu(60)
675 AnchorChanges {
676 target: notifications
677 anchors.left: parent.left
678 anchors.right: parent.right
679 }
680 },
681 State {
682 name: "wide"
683 when: overlay.width > units.gu(60)
684 AnchorChanges {
685 target: notifications
686 anchors.left: undefined
687 anchors.right: parent.right
688 }
689 PropertyChanges { target: notifications; width: units.gu(38) }
690 }
691 ]
692 }
693 }
695 Dialogs {
696 id: dialogs
697 objectName: "dialogs"
698 anchors.fill: parent
699 z: overlay.z + 10
700 usageScenario: shell.usageScenario
701 onPowerOffClicked: {
702 shutdownFadeOutRectangle.enabled = true;
703 shutdownFadeOutRectangle.visible = true;
704 shutdownFadeOut.start();
705 }
706 }
708 Connections {
709 target: SessionBroadcast
710 onShowHome: showHome()
711 }
713 ScreenGrabber {
714 id: screenGrabber
715 rotationAngle: -shell.orientationAngle
716 z: dialogs.z + 10
717 }
719 Cursor {
720 id: cursor
721 visible: shell.hasMouse
722 z: screenGrabber.z + 1
724 onPushedLeftBoundary: {
725 if (buttons === Qt.NoButton) {
726 launcher.pushEdge(amount);
727 }
728 }
730 onPushedRightBoundary: {
731 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
732 && applicationsDisplayLoader.item.pushRightEdge) {
733 applicationsDisplayLoader.item.pushRightEdge(amount);
734 }
735 }
737 onMouseMoved: { cursor.opacity = 1; }
738 }
740 Rectangle {
741 id: shutdownFadeOutRectangle
742 z: cursor.z + 1
743 enabled: false
744 visible: false
745 color: "black"
746 anchors.fill: parent
747 opacity: 0.0
748 NumberAnimation on opacity {
749 id: shutdownFadeOut
750 from: 0.0
751 to: 1.0
752 onStopped: {
753 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
754 DBusUnitySessionService.shutdown();
755 }
756 }
757 }
758 }