on dbb8563 help sign in
unity8/qml/Shell.qml
758 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 WindowInputFilter
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
45import WindowManager 0.1
48Item {
49 id: shell
51 // to be set from outside
52 property int orientationAngle: 0
53 property int orientation
54 property Orientations orientations
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 property alias oskEnabled: inputMethod.enabled
62 function updateFocusedAppOrientation() {
63 applicationsDisplayLoader.item.updateFocusedAppOrientation();
64 }
65 function updateFocusedAppOrientationAnimated() {
66 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
67 }
68 property bool hasMouse: false
70 // to be read from outside
71 readonly property int mainAppWindowOrientationAngle:
72 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
74 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
75 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
76 && (!greeter || !greeter.animating)
78 readonly property bool showingGreeter: greeter && greeter.shown
80 property bool startingUp: true
81 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
83 property int supportedOrientations: {
84 if (startingUp) {
85 // Ensure we don't rotate during start up
86 return Qt.PrimaryOrientation;
87 } else if (greeter && greeter.shown) {
88 return Qt.PrimaryOrientation;
89 } else if (applicationsDisplayLoader.item) {
90 return shell.orientations.map(applicationsDisplayLoader.item.supportedOrientations);
91 } else {
92 // we just don't care
93 return Qt.PortraitOrientation
94 | Qt.LandscapeOrientation
95 | Qt.InvertedPortraitOrientation
96 | Qt.InvertedLandscapeOrientation;
97 }
98 }
100 readonly property var mainApp:
101 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
102 onMainAppChanged: {
103 if (mainApp) {
104 _onMainAppChanged(mainApp.appId);
105 }
106 }
107 Connections {
108 target: ApplicationManager
109 onFocusRequested: {
110 if (shell.mainApp && shell.mainApp.appId === appId) {
111 _onMainAppChanged(appId);
112 }
113 }
114 }
115 function _onMainAppChanged(appId) {
116 if (wizard.active && appId != "" && appId != "unity8-dash") {
117 // If this happens on first boot, we may be in edge
118 // tutorial or wizard while receiving a call. But a call
119 // is more important than wizard so just bail out of those.
120 tutorial.finish();
121 wizard.hide();
122 }
124 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
125 // If we are in the middle of a call, make dialer lockedApp and show it.
126 // This can happen if user backs out of dialer back to greeter, then
127 // launches dialer again.
128 greeter.lockedApp = appId;
129 }
130 greeter.notifyAppFocusRequested(appId);
132 panel.indicators.hide();
133 launcher.hide();
134 }
136 // For autopilot consumption
137 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
139 // Disable everything while greeter is waiting, so that the user can't swipe
140 // the greeter or launcher until we know whether the session is locked.
141 enabled: greeter && !greeter.waiting
143 property real edgeSize: units.gu(settings.edgeDragWidth)
145 WallpaperResolver {
146 id: wallpaperResolver
147 width: shell.width
148 }
150 readonly property alias greeter: greeterLoader.item
152 function activateApplication(appId) {
153 if (ApplicationManager.findApplication(appId)) {
154 ApplicationManager.requestFocusApplication(appId);
155 } else {
156 ApplicationManager.startApplication(appId);
157 }
158 }
160 function startLockedApp(app) {
161 if (greeter.locked) {
162 greeter.lockedApp = app;
163 }
164 shell.activateApplication(app);
165 }
167 Binding {
168 target: LauncherModel
169 property: "applicationManager"
170 value: ApplicationManager
171 }
173 Component.onCompleted: {
174 theme.name = "Ubuntu.Components.Themes.SuruDark"
175 finishStartUpTimer.start();
176 }
178 LightDM{id: lightDM} // Provide backend access
179 VolumeControl {
180 id: volumeControl
181 indicators: panel.indicators
182 }
184 DashCommunicator {
185 id: dash
186 objectName: "dashCommunicator"
187 }
189 PhysicalKeysMapper {
190 id: physicalKeysMapper
191 objectName: "physicalKeysMapper"
193 onPowerKeyLongPressed: dialogs.showPowerDialog();
194 onVolumeDownTriggered: volumeControl.volumeDown();
195 onVolumeUpTriggered: volumeControl.volumeUp();
196 onScreenshotTriggered: screenGrabber.capture();
197 }
199 GlobalShortcut {
200 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
201 }
203 WindowInputFilter {
204 id: inputFilter
205 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
206 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
207 }
209 WindowInputMonitor {
210 onHomeKeyActivated: { launcher.fadeOut(); shell.showHome(); }
211 onTouchBegun: { cursor.opacity = 0; }
212 onTouchEnded: {
213 // move the (hidden) cursor to the last known touch position
214 var mappedCoords = mapFromItem(null, pos.x, pos.y);
215 cursor.x = mappedCoords.x;
216 cursor.y = mappedCoords.y;
217 }
218 }
220 GSettings {
221 id: settings
222 schema.id: "com.canonical.Unity8"
223 }
225 Item {
226 id: stages
227 objectName: "stages"
228 width: parent.width
229 height: parent.height
231 TopLevelSurfaceList {
232 id: topLevelSurfaceList
233 objectName: "topLevelSurfaceList"
234 applicationsModel: ApplicationManager
235 }
237 Loader {
238 id: applicationsDisplayLoader
239 objectName: "applicationsDisplayLoader"
240 anchors.fill: parent
242 // When we have a locked app, we only want to show that one app.
243 // FIXME: do this in a less traumatic way. We currently only allow
244 // locked apps in phone mode (see FIXME in Lockscreen component in
245 // this same file). When that changes, we need to do something
246 // nicer here. But this code is currently just to prevent a
247 // theoretical attack where user enters lockedApp mode, then makes
248 // the screen larger (maybe connects to monitor) and tries to enter
249 // tablet mode.
251 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
252 ? "phone"
253 : shell.usageScenario
254 readonly property string qmlComponent: {
255 if(shell.mode === "greeter") {
256 return "Stages/ShimStage.qml"
257 } else if (applicationsDisplayLoader.usageScenario === "phone") {
258 return "Stages/PhoneStage.qml";
259 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
260 return "Stages/TabletStage.qml";
261 } else {
262 return "Stages/DesktopStage.qml";
263 }
264 }
265 // TODO: Ensure the current stage is destroyed before the new one gets loaded.
266 // Currently the new one will get loaded while the old is still hanging
267 // around for a bit, which might lead to conflicts where both stages
268 // change the model simultaneously.
269 onQmlComponentChanged: {
270 if (item) item.stageAboutToBeUnloaded();
271 source = qmlComponent;
272 }
274 property bool interactive: (!greeter || !greeter.shown)
275 && panel.indicators.fullyClosed
276 && launcher.progress == 0
277 && !notifications.useModal
279 onInteractiveChanged: { if (interactive) { focus = true; } }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "focus"
284 value: true
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "objectName"
289 value: "stage"
290 }
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "dragAreaWidth"
294 value: shell.edgeSize
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "maximizedAppTopMargin"
299 // Not just using panel.panelHeight as that changes depending on the focused app.
300 value: panel.indicators.minimizedPanelHeight
301 }
302 Binding {
303 target: applicationsDisplayLoader.item
304 property: "interactive"
305 value: applicationsDisplayLoader.interactive
306 }
307 Binding {
308 target: applicationsDisplayLoader.item
309 property: "spreadEnabled"
310 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
311 }
312 Binding {
313 target: applicationsDisplayLoader.item
314 property: "inverseProgress"
315 value: greeter && greeter.locked ? 0 : launcher.progress
316 }
317 Binding {
318 target: applicationsDisplayLoader.item
319 property: "shellOrientationAngle"
320 value: shell.orientationAngle
321 }
322 Binding {
323 target: applicationsDisplayLoader.item
324 property: "shellOrientation"
325 value: shell.orientation
326 }
327 Binding {
328 target: applicationsDisplayLoader.item
329 property: "orientations"
330 value: shell.orientations
331 }
332 Binding {
333 target: applicationsDisplayLoader.item
334 property: "background"
335 value: wallpaperResolver.background
336 }
337 Binding {
338 target: applicationsDisplayLoader.item
339 property: "nativeWidth"
340 value: shell.nativeWidth
341 }
342 Binding {
343 target: applicationsDisplayLoader.item
344 property: "nativeHeight"
345 value: shell.nativeHeight
346 }
347 Binding {
348 target: applicationsDisplayLoader.item
349 property: "beingResized"
350 value: shell.beingResized
351 }
352 Binding {
353 target: applicationsDisplayLoader.item
354 property: "keepDashRunning"
355 value: launcher.shown || launcher.dashSwipe
356 }
357 Binding {
358 target: applicationsDisplayLoader.item
359 property: "suspended"
360 value: greeter.shown
361 }
362 Binding {
363 target: applicationsDisplayLoader.item
364 property: "altTabPressed"
365 value: physicalKeysMapper.altTabPressed
366 }
367 Binding {
368 target: applicationsDisplayLoader.item
369 property: "leftMargin"
370 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
371 }
372 Binding {
373 target: applicationsDisplayLoader.item
374 property: "applicationManager"
375 value: ApplicationManager
376 }
377 Binding {
378 target: applicationsDisplayLoader.item
379 property: "topLevelSurfaceList"
380 value: topLevelSurfaceList
381 }
382 }
383 }
385 InputMethod {
386 id: inputMethod
387 objectName: "inputMethod"
388 anchors {
389 fill: parent
390 topMargin: panel.panelHeight
391 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
392 }
393 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
394 }
396 Loader {
397 id: greeterLoader
398 anchors.fill: parent
399 anchors.topMargin: panel.panelHeight
400 sourceComponent: shell.mode != "shell" ? integratedGreeter :
401 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
402 onLoaded: {
403 item.objectName = "greeter"
404 }
405 }
407 Component {
408 id: integratedGreeter
409 Greeter {
411 hides: [launcher, panel.indicators]
412 tabletMode: shell.usageScenario != "phone"
413 launcherOffset: launcher.progress
414 forcedUnlock: wizard.active
415 background: wallpaperResolver.background
417 // avoid overlapping with Launcher's edge drag area
418 // FIXME: Fix TouchRegistry & friends and remove this workaround
419 // Issue involves launcher's DDA getting disabled on a long
420 // left-edge drag
421 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
423 onSessionStarted: {
424 launcher.hide();
425 }
427 onTease: {
428 if (!tutorial.running) {
429 launcher.tease();
430 }
431 }
433 onEmergencyCall: startLockedApp("dialer-app")
434 }
435 }
437 Timer {
438 // See powerConnection for why this is useful
439 id: showGreeterDelayed
440 interval: 1
441 onTriggered: {
442 greeter.forceShow();
443 }
444 }
446 Connections {
447 id: callConnection
448 target: callManager
450 onHasCallsChanged: {
451 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
452 // We just received an incoming call while locked. The
453 // indicator will have already launched dialer-app for us, but
454 // there is a race between "hasCalls" changing and the dialer
455 // starting up. So in case we lose that race, we'll start/
456 // focus the dialer ourselves here too. Even if the indicator
457 // didn't launch the dialer for some reason (or maybe a call
458 // started via some other means), if an active call is
459 // happening, we want to be in the dialer.
460 startLockedApp("dialer-app")
461 }
462 }
463 }
465 Connections {
466 id: powerConnection
467 target: Powerd
469 onStatusChanged: {
470 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
471 !callManager.hasCalls && !wizard.active) {
472 // We don't want to simply call greeter.showNow() here, because
473 // that will take too long. Qt will delay button event
474 // handling until the greeter is done loading and may think the
475 // user held down the power button the whole time, leading to a
476 // power dialog being shown. Instead, delay showing the
477 // greeter until we've finished handling the event. We could
478 // make the greeter load asynchronously instead, but that
479 // introduces a whole host of timing issues, especially with
480 // its animations. So this is simpler.
481 showGreeterDelayed.start();
482 }
483 }
484 }
486 function showHome() {
487 greeter.notifyUserRequestedApp("unity8-dash");
489 var animate = !lightDM.greeter.active && !stages.shown
490 dash.setCurrentScope(0, animate, false)
491 ApplicationManager.requestFocusApplication("unity8-dash")
492 }
494 function showDash() {
495 if (greeter.notifyShowingDashFromDrag()) {
496 launcher.fadeOut();
497 }
499 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
500 ApplicationManager.requestFocusApplication("unity8-dash")
501 launcher.fadeOut();
502 }
503 }
505 Item {
506 id: overlay
507 z: 10
509 anchors.fill: parent
511 Panel {
512 id: panel
513 objectName: "panel"
514 anchors.fill: parent //because this draws indicator menus
515 indicators {
516 hides: [launcher]
517 available: tutorial.panelEnabled
518 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
519 && (!greeter || !greeter.hasLockedApp)
520 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
522 minimizedPanelHeight: units.gu(3)
523 expandedPanelHeight: units.gu(7)
525 indicatorsModel: Indicators.IndicatorsModel {
526 // tablet and phone both use the same profile
527 profile: "phone"
528 Component.onCompleted: load();
529 }
530 }
532 callHint {
533 greeterShown: greeter.shown
534 }
536 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
537 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
538 : false
539 fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
540 || greeter.hasLockedApp
541 locked: greeter && greeter.active
542 }
544 Launcher {
545 id: launcher
546 objectName: "launcher"
548 readonly property bool dashSwipe: progress > 0
550 anchors.top: parent.top
551 anchors.topMargin: inverted ? 0 : panel.panelHeight
552 anchors.bottom: parent.bottom
553 width: parent.width
554 dragAreaWidth: shell.edgeSize
555 available: tutorial.launcherEnabled
556 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
557 && !greeter.hasLockedApp
558 inverted: shell.usageScenario !== "desktop"
559 superPressed: physicalKeysMapper.superPressed
560 superTabPressed: physicalKeysMapper.superTabPressed
561 panelWidth: units.gu(settings.launcherWidth)
562 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
564 onShowDashHome: showHome()
565 onDash: showDash()
566 onDashSwipeChanged: {
567 if (dashSwipe) {
568 dash.setCurrentScope(0, false, true)
569 }
570 }
571 onLauncherApplicationSelected: {
572 greeter.notifyUserRequestedApp(appId);
573 shell.activateApplication(appId);
574 }
575 onShownChanged: {
576 if (shown) {
577 panel.indicators.hide()
578 }
579 }
580 onFocusChanged: {
581 if (!focus) {
582 applicationsDisplayLoader.focus = true;
583 }
584 }
586 GlobalShortcut {
587 shortcut: Qt.AltModifier | Qt.Key_F1
588 onTriggered: {
589 launcher.openForKeyboardNavigation();
590 }
591 }
592 GlobalShortcut {
593 shortcut: Qt.MetaModifier | Qt.Key_0
594 onTriggered: {
595 if (LauncherModel.get(9)) {
596 activateApplication(LauncherModel.get(9).appId);
597 }
598 }
599 }
600 Repeater {
601 model: 9
602 GlobalShortcut {
603 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
604 onTriggered: {
605 if (LauncherModel.get(index)) {
606 activateApplication(LauncherModel.get(index).appId);
607 }
608 }
609 }
610 }
611 }
613 Tutorial {
614 id: tutorial
615 objectName: "tutorial"
616 anchors.fill: parent
618 paused: callManager.hasCalls || greeter.shown
619 keyboardVisible: inputMethod.state === "shown"
620 usageScenario: shell.usageScenario
621 lastInputTimestamp: inputFilter.lastInputTimestamp
622 launcher: launcher
623 panel: panel
624 stage: applicationsDisplayLoader.item
625 }
627 Wizard {
628 id: wizard
629 objectName: "wizard"
630 anchors.fill: parent
632 function unlockWhenDoneWithWizard() {
633 if (!active) {
634 Connectivity.unlockAllModems();
635 }
636 }
638 Component.onCompleted: unlockWhenDoneWithWizard()
639 onActiveChanged: unlockWhenDoneWithWizard()
640 }
642 Rectangle {
643 id: modalNotificationBackground
645 visible: notifications.useModal
646 color: "#000000"
647 anchors.fill: parent
648 opacity: 0.9
650 MouseArea {
651 anchors.fill: parent
652 }
653 }
655 Notifications {
656 id: notifications
658 model: NotificationBackend.Model
659 margin: units.gu(1)
660 hasMouse: shell.hasMouse
661 inverseMode: panel.indicators.shown
662 background: wallpaperResolver.background
664 y: topmostIsFullscreen ? 0 : panel.panelHeight
665 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
667 states: [
668 State {
669 name: "narrow"
670 when: overlay.width <= units.gu(60)
671 AnchorChanges {
672 target: notifications
673 anchors.left: parent.left
674 anchors.right: parent.right
675 }
676 },
677 State {
678 name: "wide"
679 when: overlay.width > units.gu(60)
680 AnchorChanges {
681 target: notifications
682 anchors.left: undefined
683 anchors.right: parent.right
684 }
685 PropertyChanges { target: notifications; width: units.gu(38) }
686 }
687 ]
688 }
689 }
691 Dialogs {
692 id: dialogs
693 objectName: "dialogs"
694 anchors.fill: parent
695 z: overlay.z + 10
696 usageScenario: shell.usageScenario
697 onPowerOffClicked: {
698 shutdownFadeOutRectangle.enabled = true;
699 shutdownFadeOutRectangle.visible = true;
700 shutdownFadeOut.start();
701 }
702 }
704 Connections {
705 target: SessionBroadcast
706 onShowHome: showHome()
707 }
709 ScreenGrabber {
710 id: screenGrabber
711 rotationAngle: -shell.orientationAngle
712 z: dialogs.z + 10
713 }
715 Cursor {
716 id: cursor
717 visible: shell.hasMouse
718 z: screenGrabber.z + 1
720 onPushedLeftBoundary: {
721 if (buttons === Qt.NoButton) {
722 launcher.pushEdge(amount);
723 }
724 }
726 onPushedRightBoundary: {
727 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
728 && applicationsDisplayLoader.item.pushRightEdge) {
729 applicationsDisplayLoader.item.pushRightEdge(amount);
730 }
731 }
733 onMouseMoved: { cursor.opacity = 1; }
734 }
736 // non-visual object
737 KeymapSwitcher {}
739 Rectangle {
740 id: shutdownFadeOutRectangle
741 z: cursor.z + 1
742 enabled: false
743 visible: false
744 color: "black"
745 anchors.fill: parent
746 opacity: 0.0
747 NumberAnimation on opacity {
748 id: shutdownFadeOut
749 from: 0.0
750 to: 1.0
751 onStopped: {
752 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
753 DBusUnitySessionService.shutdown();
754 }
755 }
756 }
757 }