on 4432516 help sign in
unity8/qml/Shell.qml
713 lines 25 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 "Stage"
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 stage.updateFocusedAppOrientation();
66 }
67 function updateFocusedAppOrientationAnimated() {
68 stage.updateFocusedAppOrientationAnimated();
69 }
70 property bool hasMouse: false
72 // to be read from outside
73 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
75 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
76 && stage.orientationChangesEnabled
77 && (!greeter || !greeter.animating)
79 readonly property bool showingGreeter: greeter && greeter.shown
81 property bool startingUp: true
82 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
84 property int supportedOrientations: {
85 if (startingUp) {
86 // Ensure we don't rotate during start up
87 return Qt.PrimaryOrientation;
88 } else if (showingGreeter || notifications.topmostIsFullscreen) {
89 return Qt.PrimaryOrientation;
90 } else {
91 return shell.orientations.map(stage.supportedOrientations);
92 }
93 }
95 readonly property var mainApp: stage.mainApp
97 onMainAppChanged: {
98 if (mainApp) {
99 _onMainAppChanged(mainApp.appId);
100 }
101 }
102 Connections {
103 target: ApplicationManager
104 onFocusRequested: {
105 if (shell.mainApp && shell.mainApp.appId === appId) {
106 _onMainAppChanged(appId);
107 }
108 }
109 }
110 function _onMainAppChanged(appId) {
111 if (wizard.active && appId != "" && appId != "unity8-dash") {
112 // If this happens on first boot, we may be in the
113 // wizard while receiving a call. But a call is more
114 // important than the wizard so just bail out of it.
115 wizard.hide();
116 }
118 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
119 // If we are in the middle of a call, make dialer lockedApp and show it.
120 // This can happen if user backs out of dialer back to greeter, then
121 // launches dialer again.
122 greeter.lockedApp = appId;
123 }
124 greeter.notifyAppFocusRequested(appId);
126 panel.indicators.hide();
127 launcher.hide();
128 }
130 // For autopilot consumption
131 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
133 // Note when greeter is waiting on PAM, so that we can disable edges until
134 // we know which user data to show and whether the session is locked.
135 readonly property bool waitingOnGreeter: greeter && greeter.waiting
137 property real edgeSize: units.gu(settings.edgeDragWidth)
139 WallpaperResolver {
140 id: wallpaperResolver
141 width: shell.width
142 }
144 readonly property alias greeter: greeterLoader.item
146 function activateApplication(appId) {
147 if (ApplicationManager.findApplication(appId)) {
148 ApplicationManager.requestFocusApplication(appId);
149 } else {
150 ApplicationManager.startApplication(appId);
151 }
152 }
154 function startLockedApp(app) {
155 if (greeter.locked) {
156 greeter.lockedApp = app;
157 }
158 shell.activateApplication(app);
159 }
161 Binding {
162 target: LauncherModel
163 property: "applicationManager"
164 value: ApplicationManager
165 }
167 Component.onCompleted: {
168 finishStartUpTimer.start();
169 }
171 VolumeControl {
172 id: volumeControl
173 indicators: panel.indicators
174 }
176 DashCommunicator {
177 id: dash
178 objectName: "dashCommunicator"
179 }
181 PhysicalKeysMapper {
182 id: physicalKeysMapper
183 objectName: "physicalKeysMapper"
185 onPowerKeyLongPressed: dialogs.showPowerDialog();
186 onVolumeDownTriggered: volumeControl.volumeDown();
187 onVolumeUpTriggered: volumeControl.volumeUp();
188 onScreenshotTriggered: itemGrabber.capture(shell);
189 }
191 GlobalShortcut {
192 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
193 }
195 WindowInputFilter {
196 id: inputFilter
197 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
198 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
199 }
201 WindowInputMonitor {
202 objectName: "windowInputMonitor"
203 onHomeKeyActivated: {
204 // Ignore when greeter is active, to avoid pocket presses
205 if (!greeter.active) {
206 launcher.fadeOut();
207 shell.showHome();
208 }
209 }
210 onTouchBegun: { cursor.opacity = 0; }
211 onTouchEnded: {
212 // move the (hidden) cursor to the last known touch position
213 var mappedCoords = mapFromItem(null, pos.x, pos.y);
214 cursor.x = mappedCoords.x;
215 cursor.y = mappedCoords.y;
216 cursor.mouseNeverMoved = false;
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 Stage {
238 id: stage
239 objectName: "stage"
240 anchors.fill: parent
241 focus: true
243 dragAreaWidth: shell.edgeSize
244 background: wallpaperResolver.background
246 applicationManager: ApplicationManager
247 topLevelSurfaceList: topLevelSurfaceList
249 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
250 ? "phone"
251 : shell.usageScenario
253 mode: usageScenario == "phone" ? "staged"
254 : usageScenario == "tablet" ? "stagedWithSideStage"
255 : "windowed"
257 shellOrientation: shell.orientation
258 shellOrientationAngle: shell.orientationAngle
259 orientations: shell.orientations
260 nativeWidth: shell.nativeWidth
261 nativeHeight: shell.nativeHeight
262 // TODO: Is this still needed? Didn't come across it. Needs checking before merging
263 beingResized: shell.beingResized
265 // TODO: Is this still needed? Didn't come across it. Needs checking before merging
266 // Not just using panel.panelHeight as that changes depending on the focused app.
267 maximizedAppTopMargin: panel.indicators.minimizedPanelHeight
269 interactive: (!greeter || !greeter.shown)
270 && panel.indicators.fullyClosed
271 && launcher.progress == 0
272 && !notifications.useModal
274 onInteractiveChanged: { if (interactive) { focus = true; } }
276 // TODO: This is not implemented yet in the new stage...
277 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
278 keepDashRunning: launcher.shown || launcher.dashSwipe
279 suspended: greeter.shown
280 leftMargin: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
282 // TODO: this is not implemented yet in the new stage...
283 inverseProgress: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
284 altTabPressed: physicalKeysMapper.altTabPressed
285 oskEnabled: shell.oskEnabled
286 }
287 }
289 InputMethod {
290 id: inputMethod
291 objectName: "inputMethod"
292 anchors {
293 fill: parent
294 topMargin: panel.panelHeight
295 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
296 }
297 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
298 }
300 Loader {
301 id: greeterLoader
302 objectName: "greeterLoader"
303 anchors.fill: parent
304 anchors.topMargin: panel.panelHeight
305 sourceComponent: shell.mode != "shell" ? integratedGreeter :
306 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
307 onLoaded: {
308 item.objectName = "greeter"
309 }
310 }
312 Component {
313 id: integratedGreeter
314 Greeter {
316 hides: [launcher, panel.indicators]
317 tabletMode: shell.usageScenario != "phone"
318 launcherOffset: launcher.progress
319 forcedUnlock: wizard.active || shell.mode === "full-shell"
320 background: wallpaperResolver.background
321 allowFingerprint: !dialogs.hasActiveDialog &&
322 !notifications.topmostIsFullscreen &&
323 !panel.indicators.shown
325 // avoid overlapping with Launcher's edge drag area
326 // FIXME: Fix TouchRegistry & friends and remove this workaround
327 // Issue involves launcher's DDA getting disabled on a long
328 // left-edge drag
329 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
331 onSessionStarted: {
332 launcher.hide();
333 }
335 onTease: {
336 if (!tutorial.running) {
337 launcher.tease();
338 }
339 }
341 onEmergencyCall: startLockedApp("dialer-app")
342 }
343 }
345 Timer {
346 // See powerConnection for why this is useful
347 id: showGreeterDelayed
348 interval: 1
349 onTriggered: {
350 greeter.forceShow();
351 }
352 }
354 Connections {
355 id: callConnection
356 target: callManager
358 onHasCallsChanged: {
359 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
360 // We just received an incoming call while locked. The
361 // indicator will have already launched dialer-app for us, but
362 // there is a race between "hasCalls" changing and the dialer
363 // starting up. So in case we lose that race, we'll start/
364 // focus the dialer ourselves here too. Even if the indicator
365 // didn't launch the dialer for some reason (or maybe a call
366 // started via some other means), if an active call is
367 // happening, we want to be in the dialer.
368 startLockedApp("dialer-app")
369 }
370 }
371 }
373 Connections {
374 id: powerConnection
375 target: Powerd
377 onStatusChanged: {
378 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
379 !callManager.hasCalls && !wizard.active) {
380 // We don't want to simply call greeter.showNow() here, because
381 // that will take too long. Qt will delay button event
382 // handling until the greeter is done loading and may think the
383 // user held down the power button the whole time, leading to a
384 // power dialog being shown. Instead, delay showing the
385 // greeter until we've finished handling the event. We could
386 // make the greeter load asynchronously instead, but that
387 // introduces a whole host of timing issues, especially with
388 // its animations. So this is simpler.
389 showGreeterDelayed.start();
390 }
391 }
392 }
394 function showHome() {
395 greeter.notifyUserRequestedApp("unity8-dash");
397 var animate = !LightDMService.greeter.active && !stages.shown
398 dash.setCurrentScope(0, animate, false)
399 ApplicationManager.requestFocusApplication("unity8-dash")
400 }
402 function showDash() {
403 if (greeter.notifyShowingDashFromDrag()) {
404 launcher.fadeOut();
405 }
407 if (!greeter.locked && tutorial.launcherLongSwipeEnabled
408 && ApplicationManager.focusedApplicationId != "unity8-dash") {
409 ApplicationManager.requestFocusApplication("unity8-dash")
410 launcher.fadeOut();
411 }
412 }
414 Item {
415 id: overlay
416 z: 10
418 anchors.fill: parent
420 Panel {
421 id: panel
422 objectName: "panel"
423 anchors.fill: parent //because this draws indicator menus
424 indicators {
425 hides: [launcher]
426 available: tutorial.panelEnabled
427 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
428 && (!greeter || !greeter.hasLockedApp)
429 && !shell.waitingOnGreeter
430 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
432 minimizedPanelHeight: units.gu(3)
433 expandedPanelHeight: units.gu(7)
435 indicatorsModel: Indicators.IndicatorsModel {
436 // tablet and phone both use the same profile
437 // FIXME: use just "phone" for greeter too, but first fix
438 // greeter app launching to either load the app inside the
439 // greeter or tell the session to load the app. This will
440 // involve taking the url-dispatcher dbus name and using
441 // SessionBroadcast to tell the session.
442 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
443 Component.onCompleted: load();
444 }
445 }
447 callHint {
448 greeterShown: greeter.shown
449 }
451 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
452 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
453 : false
454 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
455 || greeter.hasLockedApp
456 locked: greeter && greeter.active
457 }
459 Launcher {
460 id: launcher
461 objectName: "launcher"
463 /*
464 * Since the Dash doesn't have the same controll over surfaces that the
465 * Shell does, it can't slowly move the scope out of the way, as the shell
466 * does with apps, and the dash is show instantly. This allows for some
467 * leeway and prevents accidental home swipes.
468 */
469 readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
470 readonly property bool dashSwipe: progress > offset
472 anchors.top: parent.top
473 anchors.topMargin: inverted ? 0 : panel.panelHeight
474 anchors.bottom: parent.bottom
475 width: parent.width
476 dragAreaWidth: shell.edgeSize
477 available: tutorial.launcherEnabled
478 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
479 && !greeter.hasLockedApp
480 && !shell.waitingOnGreeter
481 inverted: shell.usageScenario !== "desktop"
482 superPressed: physicalKeysMapper.superPressed
483 superTabPressed: physicalKeysMapper.superTabPressed
484 panelWidth: units.gu(settings.launcherWidth)
485 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
487 onShowDashHome: showHome()
488 onDash: showDash()
489 onDashSwipeChanged: {
490 if (dashSwipe) {
491 dash.setCurrentScope(0, false, true)
492 }
493 }
494 onLauncherApplicationSelected: {
495 greeter.notifyUserRequestedApp(appId);
496 shell.activateApplication(appId);
497 }
498 onShownChanged: {
499 if (shown) {
500 panel.indicators.hide()
501 }
502 }
503 onFocusChanged: {
504 if (!focus) {
505 stage.focus = true;
506 }
507 }
509 GlobalShortcut {
510 shortcut: Qt.AltModifier | Qt.Key_F1
511 onTriggered: {
512 launcher.openForKeyboardNavigation();
513 }
514 }
515 GlobalShortcut {
516 shortcut: Qt.MetaModifier | Qt.Key_0
517 onTriggered: {
518 if (LauncherModel.get(9)) {
519 activateApplication(LauncherModel.get(9).appId);
520 }
521 }
522 }
523 Repeater {
524 model: 9
525 GlobalShortcut {
526 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
527 onTriggered: {
528 if (LauncherModel.get(index)) {
529 activateApplication(LauncherModel.get(index).appId);
530 }
531 }
532 }
533 }
534 }
536 KeyboardShortcutsOverlay {
537 objectName: "shortcutsOverlay"
538 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
539 && height < parent.height - padding - panel.panelHeight
540 anchors.centerIn: parent
541 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
542 anchors.verticalCenterOffset: panel.panelHeight/2
543 visible: opacity > 0
544 opacity: enabled ? 0.95 : 0
546 Behavior on opacity {
547 UbuntuNumberAnimation {}
548 }
549 }
551 Tutorial {
552 id: tutorial
553 objectName: "tutorial"
554 anchors.fill: parent
556 paused: callManager.hasCalls || !greeter || greeter.shown ||
557 wizard.active
558 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
559 inputMethod.visible ||
560 (launcher.shown && !launcher.lockedVisible) ||
561 panel.indicators.shown || stage.dragProgress > 0
562 usageScenario: shell.usageScenario
563 lastInputTimestamp: inputFilter.lastInputTimestamp
564 launcher: launcher
565 panel: panel
566 stage: stage
567 }
569 Wizard {
570 id: wizard
571 objectName: "wizard"
572 anchors.fill: parent
573 deferred: shell.mode === "greeter"
575 function unlockWhenDoneWithWizard() {
576 if (!active) {
577 Connectivity.unlockAllModems();
578 }
579 }
581 Component.onCompleted: unlockWhenDoneWithWizard()
582 onActiveChanged: unlockWhenDoneWithWizard()
583 }
585 MouseArea { // modal notifications prevent interacting with other contents
586 anchors.fill: parent
587 visible: notifications.useModal
588 enabled: visible
589 }
591 Notifications {
592 id: notifications
594 model: NotificationBackend.Model
595 margin: units.gu(1)
596 hasMouse: shell.hasMouse
597 background: wallpaperResolver.background
599 y: topmostIsFullscreen ? 0 : panel.panelHeight
600 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
602 states: [
603 State {
604 name: "narrow"
605 when: overlay.width <= units.gu(60)
606 AnchorChanges {
607 target: notifications
608 anchors.left: parent.left
609 anchors.right: parent.right
610 }
611 },
612 State {
613 name: "wide"
614 when: overlay.width > units.gu(60)
615 AnchorChanges {
616 target: notifications
617 anchors.left: undefined
618 anchors.right: parent.right
619 }
620 PropertyChanges { target: notifications; width: units.gu(38) }
621 }
622 ]
623 }
624 }
626 Dialogs {
627 id: dialogs
628 objectName: "dialogs"
629 anchors.fill: parent
630 z: overlay.z + 10
631 usageScenario: shell.usageScenario
632 onPowerOffClicked: {
633 shutdownFadeOutRectangle.enabled = true;
634 shutdownFadeOutRectangle.visible = true;
635 shutdownFadeOut.start();
636 }
637 }
639 Connections {
640 target: SessionBroadcast
641 onShowHome: showHome()
642 }
644 ItemGrabber {
645 id: itemGrabber
646 anchors.fill: parent
647 z: dialogs.z + 10
648 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
649 Connections {
650 target: stage
651 ignoreUnknownSignals: true
652 onItemSnapshotRequested: itemGrabber.capture(item)
653 }
654 }
656 Cursor {
657 id: cursor
658 visible: shell.hasMouse
659 z: itemGrabber.z + 1
661 property bool mouseNeverMoved: true
662 Binding {
663 target: cursor; property: "x"; value: shell.width / 2
664 when: cursor.mouseNeverMoved && cursor.visible
665 }
666 Binding {
667 target: cursor; property: "y"; value: shell.height / 2
668 when: cursor.mouseNeverMoved && cursor.visible
669 }
671 height: units.gu(3)
673 onPushedLeftBoundary: {
674 if (buttons === Qt.NoButton) {
675 launcher.pushEdge(amount);
676 }
677 }
679 onPushedRightBoundary: {
680 if (buttons === Qt.NoButton) {
681 stage.pushRightEdge(amount);
682 }
683 }
685 onMouseMoved: {
686 mouseNeverMoved = false;
687 cursor.opacity = 1;
688 }
689 }
691 // non-visual object
692 KeymapSwitcher {}
694 Rectangle {
695 id: shutdownFadeOutRectangle
696 z: cursor.z + 1
697 enabled: false
698 visible: false
699 color: "black"
700 anchors.fill: parent
701 opacity: 0.0
702 NumberAnimation on opacity {
703 id: shutdownFadeOut
704 from: 0.0
705 to: 1.0
706 onStopped: {
707 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
708 DBusUnitySessionService.shutdown();
709 }
710 }
711 }
712 }