on 0ed2df3 help sign in
unity8/qml/Shell.qml
750 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 MouseArea { // modal notifications prevent interacting with other contents
643 anchors.fill: parent
644 visible: notifications.useModal
645 enabled: visible
646 }
648 Notifications {
649 id: notifications
651 model: NotificationBackend.Model
652 margin: units.gu(1)
653 hasMouse: shell.hasMouse
654 background: wallpaperResolver.background
656 y: topmostIsFullscreen ? 0 : panel.panelHeight
657 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
659 states: [
660 State {
661 name: "narrow"
662 when: overlay.width <= units.gu(60)
663 AnchorChanges {
664 target: notifications
665 anchors.left: parent.left
666 anchors.right: parent.right
667 }
668 },
669 State {
670 name: "wide"
671 when: overlay.width > units.gu(60)
672 AnchorChanges {
673 target: notifications
674 anchors.left: undefined
675 anchors.right: parent.right
676 }
677 PropertyChanges { target: notifications; width: units.gu(38) }
678 }
679 ]
680 }
681 }
683 Dialogs {
684 id: dialogs
685 objectName: "dialogs"
686 anchors.fill: parent
687 z: overlay.z + 10
688 usageScenario: shell.usageScenario
689 onPowerOffClicked: {
690 shutdownFadeOutRectangle.enabled = true;
691 shutdownFadeOutRectangle.visible = true;
692 shutdownFadeOut.start();
693 }
694 }
696 Connections {
697 target: SessionBroadcast
698 onShowHome: showHome()
699 }
701 ScreenGrabber {
702 id: screenGrabber
703 rotationAngle: -shell.orientationAngle
704 z: dialogs.z + 10
705 }
707 Cursor {
708 id: cursor
709 visible: shell.hasMouse
710 z: screenGrabber.z + 1
712 onPushedLeftBoundary: {
713 if (buttons === Qt.NoButton) {
714 launcher.pushEdge(amount);
715 }
716 }
718 onPushedRightBoundary: {
719 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
720 && applicationsDisplayLoader.item.pushRightEdge) {
721 applicationsDisplayLoader.item.pushRightEdge(amount);
722 }
723 }
725 onMouseMoved: { cursor.opacity = 1; }
726 }
728 // non-visual object
729 KeymapSwitcher {}
731 Rectangle {
732 id: shutdownFadeOutRectangle
733 z: cursor.z + 1
734 enabled: false
735 visible: false
736 color: "black"
737 anchors.fill: parent
738 opacity: 0.0
739 NumberAnimation on opacity {
740 id: shutdownFadeOut
741 from: 0.0
742 to: 1.0
743 onStopped: {
744 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
745 DBusUnitySessionService.shutdown();
746 }
747 }
748 }
749 }