on c8fd873 help sign in
unity8/qml/Shell.qml
791 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.0
47Item {
48 id: shell
50 // to be set from outside
51 property int orientationAngle: 0
52 property int orientation
53 property Orientations orientations
54 property real nativeWidth
55 property real nativeHeight
56 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
57 property bool beingResized
58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59 property string mode: "full-greeter"
60 property alias oskEnabled: inputMethod.enabled
61 function updateFocusedAppOrientation() {
62 applicationsDisplayLoader.item.updateFocusedAppOrientation();
63 }
64 function updateFocusedAppOrientationAnimated() {
65 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
66 }
67 property bool hasMouse: false
69 // to be read from outside
70 readonly property int mainAppWindowOrientationAngle:
71 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
73 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
74 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
75 && (!greeter || !greeter.animating)
77 readonly property bool showingGreeter: greeter && greeter.shown
79 property bool startingUp: true
80 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
82 property int supportedOrientations: {
83 if (startingUp) {
84 // Ensure we don't rotate during start up
85 return Qt.PrimaryOrientation;
86 } else if (greeter && greeter.shown) {
87 return Qt.PrimaryOrientation;
88 } else if (applicationsDisplayLoader.item) {
89 return shell.orientations.map(applicationsDisplayLoader.item.supportedOrientations);
90 } else {
91 // we just don't care
92 return Qt.PortraitOrientation
93 | Qt.LandscapeOrientation
94 | Qt.InvertedPortraitOrientation
95 | Qt.InvertedLandscapeOrientation;
96 }
97 }
99 // For autopilot consumption
100 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
102 // internal props from here onwards
103 readonly property var mainApp:
104 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
105 readonly property var mainAppWindow:
106 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindow : null
108 // Disable everything while greeter is waiting, so that the user can't swipe
109 // the greeter or launcher until we know whether the session is locked.
110 enabled: greeter && !greeter.waiting
112 property real edgeSize: units.gu(2)
114 WallpaperResolver {
115 id: wallpaperResolver
116 width: shell.width
117 }
119 readonly property alias greeter: greeterLoader.item
121 function activateApplication(appId) {
122 if (ApplicationManager.findApplication(appId)) {
123 ApplicationManager.requestFocusApplication(appId);
124 } else {
125 ApplicationManager.startApplication(appId);
126 }
127 }
129 function startLockedApp(app) {
130 if (greeter.locked) {
131 greeter.lockedApp = app;
132 }
133 shell.activateApplication(app);
134 }
136 Binding {
137 target: LauncherModel
138 property: "applicationManager"
139 value: ApplicationManager
140 }
142 Component.onCompleted: {
143 theme.name = "Ubuntu.Components.Themes.SuruDark"
144 if (ApplicationManager.count > 0) {
145 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
146 }
147 finishStartUpTimer.start();
148 }
150 LightDM{id: lightDM} // Provide backend access
151 VolumeControl {
152 id: volumeControl
153 indicators: panel.indicators
154 }
156 DashCommunicator {
157 id: dash
158 objectName: "dashCommunicator"
159 }
161 PhysicalKeysMapper {
162 id: physicalKeysMapper
163 objectName: "physicalKeysMapper"
165 onPowerKeyLongPressed: dialogs.showPowerDialog();
166 onVolumeDownTriggered: volumeControl.volumeDown();
167 onVolumeUpTriggered: volumeControl.volumeUp();
168 onScreenshotTriggered: screenGrabber.capture();
169 }
171 GlobalShortcut {
172 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
173 }
175 WindowInputFilter {
176 id: inputFilter
177 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
178 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
179 }
181 WindowInputMonitor {
182 onHomeKeyActivated: { launcher.fadeOut(); shell.showHome(); }
183 onTouchBegun: { cursor.opacity = 0; }
184 onTouchEnded: {
185 // move the (hidden) cursor to the last known touch position
186 var mappedCoords = mapFromItem(null, pos.x, pos.y);
187 cursor.x = mappedCoords.x;
188 cursor.y = mappedCoords.y;
189 }
190 }
192 GSettings {
193 id: settings
194 schema.id: "com.canonical.Unity8"
195 }
197 Item {
198 id: stages
199 objectName: "stages"
200 width: parent.width
201 height: parent.height
203 Connections {
204 target: ApplicationManager
206 // This signal is also fired when we try to focus the current app
207 // again. We rely on this!
208 onFocusedApplicationIdChanged: {
209 var appId = ApplicationManager.focusedApplicationId;
211 if (wizard.active && appId != "" && appId != "unity8-dash") {
212 // If this happens on first boot, we may be in edge
213 // tutorial or wizard while receiving a call. But a call
214 // is more important than wizard so just bail out of those.
215 tutorial.finish();
216 wizard.hide();
217 }
219 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
220 // If we are in the middle of a call, make dialer lockedApp and show it.
221 // This can happen if user backs out of dialer back to greeter, then
222 // launches dialer again.
223 greeter.lockedApp = appId;
224 }
225 greeter.notifyAppFocused(appId);
227 panel.indicators.hide();
228 }
230 onApplicationAdded: {
231 launcher.hide();
232 }
233 }
235 Loader {
236 id: applicationsDisplayLoader
237 objectName: "applicationsDisplayLoader"
238 anchors.fill: parent
240 // When we have a locked app, we only want to show that one app.
241 // FIXME: do this in a less traumatic way. We currently only allow
242 // locked apps in phone mode (see FIXME in Lockscreen component in
243 // this same file). When that changes, we need to do something
244 // nicer here. But this code is currently just to prevent a
245 // theoretical attack where user enters lockedApp mode, then makes
246 // the screen larger (maybe connects to monitor) and tries to enter
247 // tablet mode.
249 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
250 ? "phone"
251 : shell.usageScenario
252 readonly property string qmlComponent: {
253 if(shell.mode === "greeter") {
254 return "Stages/ShimStage.qml"
255 } else if (applicationsDisplayLoader.usageScenario === "phone") {
256 return "Stages/PhoneStage.qml";
257 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
258 return "Stages/TabletStage.qml";
259 } else {
260 return "Stages/DesktopStage.qml";
261 }
262 }
263 onQmlComponentChanged: {
264 if (item) item.stageAboutToBeUnloaded();
265 source = qmlComponent;
266 }
268 property bool interactive: (!greeter || !greeter.shown)
269 && panel.indicators.fullyClosed
270 && launcher.progress == 0
271 && !notifications.useModal
273 onInteractiveChanged: { if (interactive) { focus = true; } }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "objectName"
278 value: "stage"
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "dragAreaWidth"
283 value: shell.edgeSize
284 }
285 Binding {
286 target: applicationsDisplayLoader.item
287 property: "maximizedAppTopMargin"
288 // Not just using panel.panelHeight as that changes depending on the focused app.
289 value: panel.indicators.minimizedPanelHeight
290 }
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "interactive"
294 value: applicationsDisplayLoader.interactive
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "spreadEnabled"
299 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "inverseProgress"
304 value: greeter && greeter.locked ? 0 : launcher.progress
305 }
306 Binding {
307 target: applicationsDisplayLoader.item
308 property: "shellOrientationAngle"
309 value: shell.orientationAngle
310 }
311 Binding {
312 target: applicationsDisplayLoader.item
313 property: "shellOrientation"
314 value: shell.orientation
315 }
316 Binding {
317 target: applicationsDisplayLoader.item
318 property: "orientations"
319 value: shell.orientations
320 }
321 Binding {
322 target: applicationsDisplayLoader.item
323 property: "background"
324 value: wallpaperResolver.background
325 }
326 Binding {
327 target: applicationsDisplayLoader.item
328 property: "nativeWidth"
329 value: shell.nativeWidth
330 }
331 Binding {
332 target: applicationsDisplayLoader.item
333 property: "nativeHeight"
334 value: shell.nativeHeight
335 }
336 Binding {
337 target: applicationsDisplayLoader.item
338 property: "beingResized"
339 value: shell.beingResized
340 }
341 Binding {
342 target: applicationsDisplayLoader.item
343 property: "keepDashRunning"
344 value: launcher.shown || launcher.dashSwipe
345 }
346 Binding {
347 target: applicationsDisplayLoader.item
348 property: "suspended"
349 value: greeter.shown
350 }
351 Binding {
352 target: applicationsDisplayLoader.item
353 property: "altTabPressed"
354 value: physicalKeysMapper.altTabPressed
355 }
356 Binding {
357 target: applicationsDisplayLoader.item
358 property: "leftMargin"
359 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
360 }
361 }
362 }
364 InputMethod {
365 id: inputMethod
366 objectName: "inputMethod"
367 anchors {
368 fill: parent
369 topMargin: panel.panelHeight
370 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
371 }
372 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
373 }
375 Connections {
376 target: SessionManager
377 onSessionStopping: {
378 if (!session.parentSession && !session.application) {
379 // nothing is using it. delete it right away
380 session.release();
381 }
382 }
383 }
385 Loader {
386 id: greeterLoader
387 anchors.fill: parent
388 anchors.topMargin: panel.panelHeight
389 sourceComponent: shell.mode != "shell" ? integratedGreeter :
390 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
391 onLoaded: {
392 item.objectName = "greeter"
393 }
394 }
396 Component {
397 id: integratedGreeter
398 Greeter {
400 hides: [launcher, panel.indicators]
401 tabletMode: shell.usageScenario != "phone"
402 launcherOffset: launcher.progress
403 forcedUnlock: wizard.active
404 background: wallpaperResolver.background
406 // avoid overlapping with Launcher's edge drag area
407 // FIXME: Fix TouchRegistry & friends and remove this workaround
408 // Issue involves launcher's DDA getting disabled on a long
409 // left-edge drag
410 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
412 onSessionStarted: {
413 launcher.hide();
414 }
416 onTease: {
417 if (!tutorial.running) {
418 launcher.tease();
419 }
420 }
422 onEmergencyCall: startLockedApp("dialer-app")
423 }
424 }
426 Timer {
427 // See powerConnection for why this is useful
428 id: showGreeterDelayed
429 interval: 1
430 onTriggered: {
431 greeter.forceShow();
432 }
433 }
435 Connections {
436 id: callConnection
437 target: callManager
439 onHasCallsChanged: {
440 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
441 // We just received an incoming call while locked. The
442 // indicator will have already launched dialer-app for us, but
443 // there is a race between "hasCalls" changing and the dialer
444 // starting up. So in case we lose that race, we'll start/
445 // focus the dialer ourselves here too. Even if the indicator
446 // didn't launch the dialer for some reason (or maybe a call
447 // started via some other means), if an active call is
448 // happening, we want to be in the dialer.
449 startLockedApp("dialer-app")
450 }
451 }
452 }
454 Connections {
455 id: powerConnection
456 target: Powerd
458 onStatusChanged: {
459 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
460 !callManager.hasCalls && !wizard.active) {
461 // We don't want to simply call greeter.showNow() here, because
462 // that will take too long. Qt will delay button event
463 // handling until the greeter is done loading and may think the
464 // user held down the power button the whole time, leading to a
465 // power dialog being shown. Instead, delay showing the
466 // greeter until we've finished handling the event. We could
467 // make the greeter load asynchronously instead, but that
468 // introduces a whole host of timing issues, especially with
469 // its animations. So this is simpler.
470 showGreeterDelayed.start();
471 }
472 }
473 }
475 function showHome() {
476 greeter.notifyAboutToFocusApp("unity8-dash");
478 var animate = !lightDM.greeter.active && !stages.shown
479 dash.setCurrentScope(0, animate, false)
480 ApplicationManager.requestFocusApplication("unity8-dash")
481 }
483 function showDash() {
484 if (greeter.notifyShowingDashFromDrag()) {
485 launcher.fadeOut();
486 }
488 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
489 ApplicationManager.requestFocusApplication("unity8-dash")
490 launcher.fadeOut();
491 }
492 }
494 Item {
495 id: overlay
496 z: 10
498 anchors.fill: parent
500 Panel {
501 id: panel
502 objectName: "panel"
503 anchors.fill: parent //because this draws indicator menus
504 indicators {
505 hides: [launcher]
506 available: tutorial.panelEnabled
507 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
508 && (!greeter || !greeter.hasLockedApp)
509 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
511 minimizedPanelHeight: units.gu(3)
512 expandedPanelHeight: units.gu(7)
514 indicatorsModel: Indicators.IndicatorsModel {
515 // tablet and phone both use the same profile
516 profile: "phone"
517 Component.onCompleted: load();
518 }
519 }
521 callHint {
522 greeterShown: greeter.shown
523 }
525 readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
527 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
528 || greeter.hasLockedApp
529 locked: greeter && greeter.active
530 }
532 Launcher {
533 id: launcher
534 objectName: "launcher"
536 readonly property bool dashSwipe: progress > 0
538 anchors.top: parent.top
539 anchors.topMargin: inverted ? 0 : panel.panelHeight
540 anchors.bottom: parent.bottom
541 width: parent.width
542 dragAreaWidth: shell.edgeSize
543 available: tutorial.launcherEnabled
544 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
545 && !greeter.hasLockedApp
546 inverted: shell.usageScenario !== "desktop"
547 superPressed: physicalKeysMapper.superPressed
548 superTabPressed: physicalKeysMapper.superTabPressed
549 panelWidth: units.gu(settings.launcherWidth)
550 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
552 onShowDashHome: showHome()
553 onDash: showDash()
554 onDashSwipeChanged: {
555 if (dashSwipe) {
556 dash.setCurrentScope(0, false, true)
557 }
558 }
559 onLauncherApplicationSelected: {
560 greeter.notifyAboutToFocusApp(appId);
561 shell.activateApplication(appId);
562 }
563 onShownChanged: {
564 if (shown) {
565 panel.indicators.hide()
566 }
567 }
568 onFocusChanged: {
569 if (!focus) {
570 applicationsDisplayLoader.focus = true;
571 }
572 }
574 GlobalShortcut {
575 shortcut: Qt.AltModifier | Qt.Key_F1
576 onTriggered: {
577 launcher.openForKeyboardNavigation();
578 }
579 }
580 GlobalShortcut {
581 shortcut: Qt.MetaModifier | Qt.Key_0
582 onTriggered: {
583 if (LauncherModel.get(9)) {
584 activateApplication(LauncherModel.get(9).appId);
585 }
586 }
587 }
588 Repeater {
589 model: 9
590 GlobalShortcut {
591 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
592 onTriggered: {
593 if (LauncherModel.get(index)) {
594 activateApplication(LauncherModel.get(index).appId);
595 }
596 }
597 }
598 }
599 }
601 Tutorial {
602 id: tutorial
603 objectName: "tutorial"
604 anchors.fill: parent
606 paused: callManager.hasCalls || greeter.shown
607 keyboardVisible: inputMethod.state === "shown"
608 usageScenario: shell.usageScenario
609 lastInputTimestamp: inputFilter.lastInputTimestamp
610 launcher: launcher
611 panel: panel
612 stage: applicationsDisplayLoader.item
613 }
615 Wizard {
616 id: wizard
617 objectName: "wizard"
618 anchors.fill: parent
620 function unlockWhenDoneWithWizard() {
621 if (!active) {
622 Connectivity.unlockAllModems();
623 }
624 }
626 Component.onCompleted: unlockWhenDoneWithWizard()
627 onActiveChanged: unlockWhenDoneWithWizard()
628 }
630 Rectangle {
631 id: modalNotificationBackground
633 visible: notifications.useModal
634 color: "#000000"
635 anchors.fill: parent
636 opacity: 0.9
638 MouseArea {
639 anchors.fill: parent
640 }
641 }
643 Notifications {
644 id: notifications
646 model: NotificationBackend.Model
647 margin: units.gu(1)
648 hasMouse: shell.hasMouse
649 inverseMode: panel.indicators.shown
650 background: wallpaperResolver.background
652 y: topmostIsFullscreen ? 0 : panel.panelHeight
653 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
655 states: [
656 State {
657 name: "narrow"
658 when: overlay.width <= units.gu(60)
659 AnchorChanges {
660 target: notifications
661 anchors.left: parent.left
662 anchors.right: parent.right
663 }
664 },
665 State {
666 name: "wide"
667 when: overlay.width > units.gu(60)
668 AnchorChanges {
669 target: notifications
670 anchors.left: undefined
671 anchors.right: parent.right
672 }
673 PropertyChanges { target: notifications; width: units.gu(38) }
674 }
675 ]
676 }
677 }
679 Dialogs {
680 id: dialogs
681 objectName: "dialogs"
682 anchors.fill: parent
683 z: overlay.z + 10
684 usageScenario: shell.usageScenario
685 onPowerOffClicked: {
686 shutdownFadeOutRectangle.enabled = true;
687 shutdownFadeOutRectangle.visible = true;
688 shutdownFadeOut.start();
689 }
690 }
692 Connections {
693 target: SessionBroadcast
694 onShowHome: showHome()
695 }
697 ScreenGrabber {
698 id: screenGrabber
699 rotationAngle: -shell.orientationAngle
700 z: dialogs.z + 10
701 }
703 Cursor {
704 id: cursor
705 visible: shell.hasMouse
706 z: screenGrabber.z + 1
708 onPushedLeftBoundary: {
709 if (buttons === Qt.NoButton) {
710 launcher.pushEdge(amount);
711 }
712 }
714 onPushedRightBoundary: {
715 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
716 && applicationsDisplayLoader.item.pushRightEdge) {
717 applicationsDisplayLoader.item.pushRightEdge(amount);
718 }
719 }
721 onMouseMoved: { cursor.opacity = 1; }
722 }
724 // keymap switching
725 GlobalShortcut {
726 shortcut: Qt.MetaModifier|Qt.Key_Space
727 onTriggered: keymapPriv.nextKeymap()
728 active: keymapPriv.keymapCount > 1
729 }
731 GlobalShortcut {
732 shortcut: Qt.MetaModifier|Qt.ShiftModifier|Qt.Key_Space
733 onTriggered: keymapPriv.previousKeymap()
734 active: keymapPriv.keymapCount > 1
735 }
737 QtObject {
738 id: keymapPriv
740 readonly property var keymaps: AccountsService.keymaps
741 readonly property int keymapCount: keymaps.length
742 property int currentKeymapIndex: 0 // the new one that we're setting
743 onCurrentKeymapIndexChanged: switchToKeymap();
745 function nextKeymap() {
746 var nextIndex = 0;
748 if (currentKeymapIndex !== -1 && currentKeymapIndex < keymapCount - 1) {
749 nextIndex = currentKeymapIndex + 1;
750 }
751 currentKeymapIndex = nextIndex;
752 }
754 function previousKeymap() {
755 var prevIndex = keymapCount - 1;
757 if (currentKeymapIndex > 0) {
758 prevIndex = currentKeymapIndex - 1;
759 }
760 currentKeymapIndex = prevIndex;
761 }
763 function switchToKeymap() {
764 if (mainAppWindow) {
765 mainAppWindow.switchToKeymap(keymaps[currentKeymapIndex]);
766 }
767 }
768 }
770 onMainAppWindowChanged: keymapPriv.switchToKeymap()
772 Rectangle {
773 id: shutdownFadeOutRectangle
774 z: cursor.z + 1
775 enabled: false
776 visible: false
777 color: "black"
778 anchors.fill: parent
779 opacity: 0.0
780 NumberAnimation on opacity {
781 id: shutdownFadeOut
782 from: 0.0
783 to: 1.0
784 onStopped: {
785 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
786 DBusUnitySessionService.shutdown();
787 }
788 }
789 }
790 }