on f909fbf help sign in
unity8/qml/Shell.qml
764 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
261 readonly property string qmlComponent: {
262 if(shell.mode === "greeter") {
263 return "Stages/ShimStage.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 active: oskEnabled
396 anchors {
397 fill: parent
398 topMargin: panel.panelHeight
399 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
400 }
401 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
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: wizard.active
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 && !wizard.active) {
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 greeter.notifyUserRequestedApp("unity8-dash");
497 var animate = !lightDM.greeter.active && !stages.shown
498 dash.setCurrentScope(0, animate, false)
499 ApplicationManager.requestFocusApplication("unity8-dash")
500 }
502 function showDash() {
503 if (greeter.notifyShowingDashFromDrag()) {
504 launcher.fadeOut();
505 }
507 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
508 ApplicationManager.requestFocusApplication("unity8-dash")
509 launcher.fadeOut();
510 }
511 }
513 Item {
514 id: overlay
515 z: 10
517 anchors.fill: parent
519 Panel {
520 id: panel
521 objectName: "panel"
522 anchors.fill: parent //because this draws indicator menus
523 indicators {
524 hides: [launcher]
525 available: tutorial.panelEnabled
526 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
527 && (!greeter || !greeter.hasLockedApp)
528 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
530 minimizedPanelHeight: units.gu(3)
531 expandedPanelHeight: units.gu(7)
533 indicatorsModel: Indicators.IndicatorsModel {
534 // tablet and phone both use the same profile
535 profile: "phone"
536 Component.onCompleted: load();
537 }
538 }
540 callHint {
541 greeterShown: greeter.shown
542 }
544 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
545 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
546 : false
547 fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
548 || greeter.hasLockedApp
549 locked: greeter && greeter.active
550 }
552 Launcher {
553 id: launcher
554 objectName: "launcher"
556 readonly property bool dashSwipe: progress > 0
558 anchors.top: parent.top
559 anchors.topMargin: inverted ? 0 : panel.panelHeight
560 anchors.bottom: parent.bottom
561 width: parent.width
562 dragAreaWidth: shell.edgeSize
563 available: tutorial.launcherEnabled
564 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
565 && !greeter.hasLockedApp
566 inverted: shell.usageScenario !== "desktop"
567 superPressed: physicalKeysMapper.superPressed
568 superTabPressed: physicalKeysMapper.superTabPressed
569 panelWidth: units.gu(settings.launcherWidth)
570 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
572 onShowDashHome: showHome()
573 onDash: showDash()
574 onDashSwipeChanged: {
575 if (dashSwipe) {
576 dash.setCurrentScope(0, false, true)
577 }
578 }
579 onLauncherApplicationSelected: {
580 greeter.notifyUserRequestedApp(appId);
581 shell.activateApplication(appId);
582 }
583 onShownChanged: {
584 if (shown) {
585 panel.indicators.hide()
586 }
587 }
588 onFocusChanged: {
589 if (!focus) {
590 applicationsDisplayLoader.focus = true;
591 }
592 }
594 GlobalShortcut {
595 shortcut: Qt.AltModifier | Qt.Key_F1
596 onTriggered: {
597 launcher.openForKeyboardNavigation();
598 }
599 }
600 GlobalShortcut {
601 shortcut: Qt.MetaModifier | Qt.Key_0
602 onTriggered: {
603 if (LauncherModel.get(9)) {
604 activateApplication(LauncherModel.get(9).appId);
605 }
606 }
607 }
608 Repeater {
609 model: 9
610 GlobalShortcut {
611 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
612 onTriggered: {
613 if (LauncherModel.get(index)) {
614 activateApplication(LauncherModel.get(index).appId);
615 }
616 }
617 }
618 }
619 }
621 Tutorial {
622 id: tutorial
623 objectName: "tutorial"
624 anchors.fill: parent
626 paused: callManager.hasCalls || greeter.shown
627 keyboardVisible: inputMethod.state === "shown"
628 usageScenario: shell.usageScenario
629 lastInputTimestamp: inputFilter.lastInputTimestamp
630 launcher: launcher
631 panel: panel
632 stage: applicationsDisplayLoader.item
633 }
635 Wizard {
636 id: wizard
637 objectName: "wizard"
638 anchors.fill: parent
640 function unlockWhenDoneWithWizard() {
641 if (!active) {
642 Connectivity.unlockAllModems();
643 }
644 }
646 Component.onCompleted: unlockWhenDoneWithWizard()
647 onActiveChanged: unlockWhenDoneWithWizard()
648 }
650 MouseArea { // modal notifications prevent interacting with other contents
651 anchors.fill: parent
652 visible: notifications.useModal
653 enabled: visible
654 }
656 Notifications {
657 id: notifications
659 model: NotificationBackend.Model
660 margin: units.gu(1)
661 hasMouse: shell.hasMouse
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 ItemGrabber {
710 id: itemGrabber
711 anchors.fill: parent
712 z: dialogs.z + 10
713 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
714 Connections {
715 target: applicationsDisplayLoader.item
716 ignoreUnknownSignals: true
717 onItemSnapshotRequested: itemGrabber.capture(item)
718 }
719 }
721 Cursor {
722 id: cursor
723 visible: shell.hasMouse
724 z: itemGrabber.z + 1
726 onPushedLeftBoundary: {
727 if (buttons === Qt.NoButton) {
728 launcher.pushEdge(amount);
729 }
730 }
732 onPushedRightBoundary: {
733 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
734 && applicationsDisplayLoader.item.pushRightEdge) {
735 applicationsDisplayLoader.item.pushRightEdge(amount);
736 }
737 }
739 onMouseMoved: { cursor.opacity = 1; }
740 }
742 // non-visual object
743 KeymapSwitcher {}
745 Rectangle {
746 id: shutdownFadeOutRectangle
747 z: cursor.z + 1
748 enabled: false
749 visible: false
750 color: "black"
751 anchors.fill: parent
752 opacity: 0.0
753 NumberAnimation on opacity {
754 id: shutdownFadeOut
755 from: 0.0
756 to: 1.0
757 onStopped: {
758 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
759 DBusUnitySessionService.shutdown();
760 }
761 }
762 }
763 }