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