on 4547ec1 help sign in
unity8/qml/Shell.qml
762 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: 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
262 source: "Stages/DesktopStage.qml"
263 readonly property string qmlComponent: {
264 if(shell.mode === "greeter") {
265 return "Stages/ShimStage.qml"
266 } else if (applicationsDisplayLoader.usageScenario === "phone") {
267 return "Stages/PhoneStage.qml";
268 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
269 return "Stages/TabletStage.qml";
270 } else {
271 return "Stages/DesktopStage.qml";
272 }
273 }
275 property bool interactive: (!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: "mode"
285 value: applicationsDisplayLoader.usageScenario == "phone" ? "staged" : "windowed"
286 }
287 Binding {
288 target: applicationsDisplayLoader.item
289 property: "focus"
290 value: true
291 }
292 Binding {
293 target: applicationsDisplayLoader.item
294 property: "objectName"
295 value: "stage"
296 }
297 Binding {
298 target: applicationsDisplayLoader.item
299 property: "dragAreaWidth"
300 value: shell.edgeSize
301 }
302 Binding {
303 target: applicationsDisplayLoader.item
304 property: "maximizedAppTopMargin"
305 // Not just using panel.panelHeight as that changes depending on the focused app.
306 value: panel.indicators.minimizedPanelHeight
307 }
308 Binding {
309 target: applicationsDisplayLoader.item
310 property: "interactive"
311 value: applicationsDisplayLoader.interactive
312 }
313 Binding {
314 target: applicationsDisplayLoader.item
315 property: "spreadEnabled"
316 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
317 }
318 Binding {
319 target: applicationsDisplayLoader.item
320 property: "inverseProgress"
321 value: greeter && greeter.locked ? 0 : launcher.progress
322 }
323 Binding {
324 target: applicationsDisplayLoader.item
325 property: "shellOrientationAngle"
326 value: shell.orientationAngle
327 }
328 Binding {
329 target: applicationsDisplayLoader.item
330 property: "shellOrientation"
331 value: shell.orientation
332 }
333 Binding {
334 target: applicationsDisplayLoader.item
335 property: "orientations"
336 value: shell.orientations
337 }
338 Binding {
339 target: applicationsDisplayLoader.item
340 property: "background"
341 value: wallpaperResolver.background
342 }
343 Binding {
344 target: applicationsDisplayLoader.item
345 property: "nativeWidth"
346 value: shell.nativeWidth
347 }
348 Binding {
349 target: applicationsDisplayLoader.item
350 property: "nativeHeight"
351 value: shell.nativeHeight
352 }
353 Binding {
354 target: applicationsDisplayLoader.item
355 property: "beingResized"
356 value: shell.beingResized
357 }
358 Binding {
359 target: applicationsDisplayLoader.item
360 property: "keepDashRunning"
361 value: launcher.shown || launcher.dashSwipe
362 }
363 Binding {
364 target: applicationsDisplayLoader.item
365 property: "suspended"
366 value: greeter.shown
367 }
368 Binding {
369 target: applicationsDisplayLoader.item
370 property: "altTabPressed"
371 value: physicalKeysMapper.altTabPressed
372 }
373 Binding {
374 target: applicationsDisplayLoader.item
375 property: "leftMargin"
376 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
377 }
378 Binding {
379 target: applicationsDisplayLoader.item
380 property: "applicationManager"
381 value: ApplicationManager
382 }
383 Binding {
384 target: applicationsDisplayLoader.item
385 property: "topLevelSurfaceList"
386 value: topLevelSurfaceList
387 }
388 }
389 }
391 InputMethod {
392 id: inputMethod
393 objectName: "inputMethod"
394 anchors {
395 fill: parent
396 topMargin: panel.panelHeight
397 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
398 }
399 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
400 }
402 Loader {
403 id: greeterLoader
404 anchors.fill: parent
405 anchors.topMargin: panel.panelHeight
406 sourceComponent: shell.mode != "shell" ? integratedGreeter :
407 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
408 onLoaded: {
409 item.objectName = "greeter"
410 }
411 }
413 Component {
414 id: integratedGreeter
415 Greeter {
417 hides: [launcher, panel.indicators]
418 tabletMode: shell.usageScenario != "phone"
419 launcherOffset: launcher.progress
420 forcedUnlock: wizard.active
421 background: wallpaperResolver.background
423 // avoid overlapping with Launcher's edge drag area
424 // FIXME: Fix TouchRegistry & friends and remove this workaround
425 // Issue involves launcher's DDA getting disabled on a long
426 // left-edge drag
427 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
429 onSessionStarted: {
430 launcher.hide();
431 }
433 onTease: {
434 if (!tutorial.running) {
435 launcher.tease();
436 }
437 }
439 onEmergencyCall: startLockedApp("dialer-app")
440 }
441 }
443 Timer {
444 // See powerConnection for why this is useful
445 id: showGreeterDelayed
446 interval: 1
447 onTriggered: {
448 greeter.forceShow();
449 }
450 }
452 Connections {
453 id: callConnection
454 target: callManager
456 onHasCallsChanged: {
457 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
458 // We just received an incoming call while locked. The
459 // indicator will have already launched dialer-app for us, but
460 // there is a race between "hasCalls" changing and the dialer
461 // starting up. So in case we lose that race, we'll start/
462 // focus the dialer ourselves here too. Even if the indicator
463 // didn't launch the dialer for some reason (or maybe a call
464 // started via some other means), if an active call is
465 // happening, we want to be in the dialer.
466 startLockedApp("dialer-app")
467 }
468 }
469 }
471 Connections {
472 id: powerConnection
473 target: Powerd
475 onStatusChanged: {
476 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
477 !callManager.hasCalls && !wizard.active) {
478 // We don't want to simply call greeter.showNow() here, because
479 // that will take too long. Qt will delay button event
480 // handling until the greeter is done loading and may think the
481 // user held down the power button the whole time, leading to a
482 // power dialog being shown. Instead, delay showing the
483 // greeter until we've finished handling the event. We could
484 // make the greeter load asynchronously instead, but that
485 // introduces a whole host of timing issues, especially with
486 // its animations. So this is simpler.
487 showGreeterDelayed.start();
488 }
489 }
490 }
492 function showHome() {
493 greeter.notifyUserRequestedApp("unity8-dash");
495 var animate = !lightDM.greeter.active && !stages.shown
496 dash.setCurrentScope(0, animate, false)
497 ApplicationManager.requestFocusApplication("unity8-dash")
498 }
500 function showDash() {
501 if (greeter.notifyShowingDashFromDrag()) {
502 launcher.fadeOut();
503 }
505 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
506 ApplicationManager.requestFocusApplication("unity8-dash")
507 launcher.fadeOut();
508 }
509 }
511 Item {
512 id: overlay
513 z: 10
515 anchors.fill: parent
517 Panel {
518 id: panel
519 objectName: "panel"
520 anchors.fill: parent //because this draws indicator menus
521 indicators {
522 hides: [launcher]
523 available: tutorial.panelEnabled
524 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
525 && (!greeter || !greeter.hasLockedApp)
526 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
528 minimizedPanelHeight: units.gu(3)
529 expandedPanelHeight: units.gu(7)
531 indicatorsModel: Indicators.IndicatorsModel {
532 // tablet and phone both use the same profile
533 profile: "phone"
534 Component.onCompleted: load();
535 }
536 }
538 callHint {
539 greeterShown: greeter.shown
540 }
542 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
543 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
544 : false
545 fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
546 || greeter.hasLockedApp
547 locked: greeter && greeter.active
548 }
550 Launcher {
551 id: launcher
552 objectName: "launcher"
554 readonly property bool dashSwipe: progress > 0
556 anchors.top: parent.top
557 anchors.topMargin: inverted ? 0 : panel.panelHeight
558 anchors.bottom: parent.bottom
559 width: parent.width
560 dragAreaWidth: shell.edgeSize
561 available: tutorial.launcherEnabled
562 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
563 && !greeter.hasLockedApp
564 inverted: shell.usageScenario !== "desktop"
565 superPressed: physicalKeysMapper.superPressed
566 superTabPressed: physicalKeysMapper.superTabPressed
567 panelWidth: units.gu(settings.launcherWidth)
568 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
570 onShowDashHome: showHome()
571 onDash: showDash()
572 onDashSwipeChanged: {
573 if (dashSwipe) {
574 dash.setCurrentScope(0, false, true)
575 }
576 }
577 onLauncherApplicationSelected: {
578 greeter.notifyUserRequestedApp(appId);
579 shell.activateApplication(appId);
580 }
581 onShownChanged: {
582 if (shown) {
583 panel.indicators.hide()
584 }
585 }
586 onFocusChanged: {
587 if (!focus) {
588 applicationsDisplayLoader.focus = true;
589 }
590 }
592 GlobalShortcut {
593 shortcut: Qt.AltModifier | Qt.Key_F1
594 onTriggered: {
595 launcher.openForKeyboardNavigation();
596 }
597 }
598 GlobalShortcut {
599 shortcut: Qt.MetaModifier | Qt.Key_0
600 onTriggered: {
601 if (LauncherModel.get(9)) {
602 activateApplication(LauncherModel.get(9).appId);
603 }
604 }
605 }
606 Repeater {
607 model: 9
608 GlobalShortcut {
609 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
610 onTriggered: {
611 if (LauncherModel.get(index)) {
612 activateApplication(LauncherModel.get(index).appId);
613 }
614 }
615 }
616 }
617 }
619 Tutorial {
620 id: tutorial
621 objectName: "tutorial"
622 anchors.fill: parent
624 paused: callManager.hasCalls || greeter.shown
625 keyboardVisible: inputMethod.state === "shown"
626 usageScenario: shell.usageScenario
627 lastInputTimestamp: inputFilter.lastInputTimestamp
628 launcher: launcher
629 panel: panel
630 stage: applicationsDisplayLoader.item
631 }
633 Wizard {
634 id: wizard
635 objectName: "wizard"
636 anchors.fill: parent
638 function unlockWhenDoneWithWizard() {
639 if (!active) {
640 Connectivity.unlockAllModems();
641 }
642 }
644 Component.onCompleted: unlockWhenDoneWithWizard()
645 onActiveChanged: unlockWhenDoneWithWizard()
646 }
648 MouseArea { // modal notifications prevent interacting with other contents
649 anchors.fill: parent
650 visible: notifications.useModal
651 enabled: visible
652 }
654 Notifications {
655 id: notifications
657 model: NotificationBackend.Model
658 margin: units.gu(1)
659 hasMouse: shell.hasMouse
660 background: wallpaperResolver.background
662 y: topmostIsFullscreen ? 0 : panel.panelHeight
663 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
665 states: [
666 State {
667 name: "narrow"
668 when: overlay.width <= units.gu(60)
669 AnchorChanges {
670 target: notifications
671 anchors.left: parent.left
672 anchors.right: parent.right
673 }
674 },
675 State {
676 name: "wide"
677 when: overlay.width > units.gu(60)
678 AnchorChanges {
679 target: notifications
680 anchors.left: undefined
681 anchors.right: parent.right
682 }
683 PropertyChanges { target: notifications; width: units.gu(38) }
684 }
685 ]
686 }
687 }
689 Dialogs {
690 id: dialogs
691 objectName: "dialogs"
692 anchors.fill: parent
693 z: overlay.z + 10
694 usageScenario: shell.usageScenario
695 onPowerOffClicked: {
696 shutdownFadeOutRectangle.enabled = true;
697 shutdownFadeOutRectangle.visible = true;
698 shutdownFadeOut.start();
699 }
700 }
702 Connections {
703 target: SessionBroadcast
704 onShowHome: showHome()
705 }
707 ItemGrabber {
708 id: itemGrabber
709 anchors.fill: parent
710 z: dialogs.z + 10
711 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
712 Connections {
713 target: applicationsDisplayLoader.item
714 ignoreUnknownSignals: true
715 onItemSnapshotRequested: itemGrabber.capture(item)
716 }
717 }
719 Cursor {
720 id: cursor
721 visible: shell.hasMouse
722 z: itemGrabber.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 // non-visual object
741 KeymapSwitcher {}
743 Rectangle {
744 id: shutdownFadeOutRectangle
745 z: cursor.z + 1
746 enabled: false
747 visible: false
748 color: "black"
749 anchors.fill: parent
750 opacity: 0.0
751 NumberAnimation on opacity {
752 id: shutdownFadeOut
753 from: 0.0
754 to: 1.0
755 onStopped: {
756 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
757 DBusUnitySessionService.shutdown();
758 }
759 }
760 }
761 }