2 * Copyright (C) 2013-2016 Canonical, Ltd.
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.
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.
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/>.
18 import QtQuick.Window 2.2
19 import AccountsService 0.1
20 import Unity.Application 0.1
21 import Ubuntu.Components 1.3
22 import Ubuntu.Components.Popups 1.3
23 import Ubuntu.Gestures 0.1
24 import Ubuntu.Telephony 0.1 as Telephony
25 import Unity.Connectivity 0.1
26 import Unity.Launcher 0.1
27 import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
32 import SessionBroadcast 0.1
37 import "Notifications"
41 import Unity.Notifications 1.0 as NotificationBackend
42 import Unity.Session 0.1
43 import Unity.DashCommunicator 0.1
44 import Unity.Indicators 0.1 as Indicators
46 import WindowManager 0.1
52 theme.name: "Ubuntu.Components.Themes.SuruDark"
54 // to be set from outside
55 property int orientationAngle: 0
56 property int orientation
57 property Orientations orientations
58 property real nativeWidth
59 property real nativeHeight
60 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
61 property bool beingResized
62 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
63 property string mode: "full-greeter"
64 property alias oskEnabled: inputMethod.enabled
65 function updateFocusedAppOrientation() {
66 applicationsDisplayLoader.item.updateFocusedAppOrientation();
68 function updateFocusedAppOrientationAnimated() {
69 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
71 property bool hasMouse: false
73 // to be read from outside
74 readonly property int mainAppWindowOrientationAngle:
75 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
77 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
78 && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
79 && (!greeter || !greeter.animating)
81 readonly property bool showingGreeter: greeter && greeter.shown
83 property bool startingUp: true
84 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
86 property int supportedOrientations: {
88 // Ensure we don't rotate during start up
89 return Qt.PrimaryOrientation;
90 } else if (showingGreeter || notifications.topmostIsFullscreen) {
91 return Qt.PrimaryOrientation;
92 } else if (applicationsDisplayLoader.item) {
93 return shell.orientations.map(applicationsDisplayLoader.item.supportedOrientations);
96 return Qt.PortraitOrientation
97 | Qt.LandscapeOrientation
98 | Qt.InvertedPortraitOrientation
99 | Qt.InvertedLandscapeOrientation;
103 readonly property var mainApp:
104 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
107 _onMainAppChanged(mainApp.appId);
111 target: ApplicationManager
113 if (shell.mainApp && shell.mainApp.appId === appId) {
114 _onMainAppChanged(appId);
118 function _onMainAppChanged(appId) {
119 if (wizard.active && appId != "" && appId != "unity8-dash") {
120 // If this happens on first boot, we may be in the
121 // wizard while receiving a call. But a call is more
122 // important than the wizard so just bail out of it.
126 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
127 // If we are in the middle of a call, make dialer lockedApp and show it.
128 // This can happen if user backs out of dialer back to greeter, then
129 // launches dialer again.
130 greeter.lockedApp = appId;
132 greeter.notifyAppFocusRequested(appId);
134 panel.indicators.hide();
138 // For autopilot consumption
139 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
141 // Note when greeter is waiting on PAM, so that we can disable edges until
142 // we know which user data to show and whether the session is locked.
143 readonly property bool waitingOnGreeter: greeter && greeter.waiting
145 property real edgeSize: units.gu(settings.edgeDragWidth)
148 id: wallpaperResolver
149 objectName: "wallpaperResolver"
151 readonly property url defaultBackground: "file:///usr/share/backgrounds/warty-final-ubuntu.png"
152 readonly property bool hasCustomBackground: background != defaultBackground
154 // Use a cached version of the scaled-down wallpaper (as sometimes the
155 // image can be quite big compared to the device size, including for
156 // our default wallpaper). We use a name=wallpaper argument here to
157 // make sure we don't litter our cache with lots of scaled images. We
158 // only need to bother caching one at a time.
159 readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
162 id: backgroundSettings
163 schema.id: "org.gnome.desktop.background"
167 AccountsService.backgroundFile,
168 backgroundSettings.pictureUri,
173 readonly property alias greeter: greeterLoader.item
175 function activateApplication(appId) {
176 // Either open the app in our own session, or -- if we're acting as a
177 // greeter -- ask the user's session to open it for us.
178 if (shell.mode === "greeter") {
179 activateURL("application:///" + appId + ".desktop");
185 function activateURL(url) {
186 SessionBroadcast.requestUrlStart(AccountsService.user, url);
187 greeter.notifyUserRequestedApp();
188 panel.indicators.hide();
191 function startApp(appId) {
192 if (ApplicationManager.findApplication(appId)) {
193 ApplicationManager.requestFocusApplication(appId);
195 ApplicationManager.startApplication(appId);
199 function startLockedApp(app) {
200 if (greeter.locked) {
201 greeter.lockedApp = app;
203 startApp(app); // locked apps are always in our same session
207 target: LauncherModel
208 property: "applicationManager"
209 value: ApplicationManager
212 Component.onCompleted: {
213 finishStartUpTimer.start();
222 objectName: "dashCommunicator"
226 id: physicalKeysMapper
227 objectName: "physicalKeysMapper"
229 onPowerKeyLongPressed: dialogs.showPowerDialog();
230 onVolumeDownTriggered: volumeControl.volumeDown();
231 onVolumeUpTriggered: volumeControl.volumeUp();
232 onScreenshotTriggered: itemGrabber.capture(shell);
236 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
241 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
242 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
246 objectName: "windowInputMonitor"
247 onHomeKeyActivated: {
248 // Ignore when greeter is active, to avoid pocket presses
249 if (!greeter.active) {
254 onTouchBegun: { cursor.opacity = 0; }
256 // move the (hidden) cursor to the last known touch position
257 var mappedCoords = mapFromItem(null, pos.x, pos.y);
258 cursor.x = mappedCoords.x;
259 cursor.y = mappedCoords.y;
260 cursor.mouseNeverMoved = false;
266 schema.id: "com.canonical.Unity8"
273 height: parent.height
275 TopLevelSurfaceList {
276 id: topLevelSurfaceList
277 objectName: "topLevelSurfaceList"
278 applicationsModel: ApplicationManager
282 id: applicationsDisplayLoader
283 objectName: "applicationsDisplayLoader"
286 // When we have a locked app, we only want to show that one app.
287 // FIXME: do this in a less traumatic way. We currently only allow
288 // locked apps in phone mode (see FIXME in Lockscreen component in
289 // this same file). When that changes, we need to do something
290 // nicer here. But this code is currently just to prevent a
291 // theoretical attack where user enters lockedApp mode, then makes
292 // the screen larger (maybe connects to monitor) and tries to enter
295 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
297 : shell.usageScenario
298 readonly property string qmlComponent: {
299 if (applicationsDisplayLoader.usageScenario === "phone") {
300 return "Stages/PhoneStage.qml";
301 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
302 return "Stages/TabletStage.qml";
304 return "Stages/DesktopStage.qml";
307 // TODO: Ensure the current stage is destroyed before the new one gets loaded.
308 // Currently the new one will get loaded while the old is still hanging
309 // around for a bit, which might lead to conflicts where both stages
310 // change the model simultaneously.
311 onQmlComponentChanged: {
312 if (item) item.stageAboutToBeUnloaded();
313 source = qmlComponent;
316 property bool interactive: (!greeter || !greeter.shown)
317 && panel.indicators.fullyClosed
318 && launcher.progress == 0
319 && !notifications.useModal
321 onInteractiveChanged: { if (interactive) { focus = true; } }
324 target: applicationsDisplayLoader.item
329 target: applicationsDisplayLoader.item
330 property: "objectName"
334 target: applicationsDisplayLoader.item
335 property: "dragAreaWidth"
336 value: shell.edgeSize
339 target: applicationsDisplayLoader.item
340 property: "maximizedAppTopMargin"
341 // Not just using panel.panelHeight as that changes depending on the focused app.
342 value: panel.indicators.minimizedPanelHeight
345 target: applicationsDisplayLoader.item
346 property: "interactive"
347 value: applicationsDisplayLoader.interactive
350 target: applicationsDisplayLoader.item
351 property: "spreadEnabled"
352 value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
355 target: applicationsDisplayLoader.item
356 property: "inverseProgress"
357 value: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
360 target: applicationsDisplayLoader.item
361 property: "shellOrientationAngle"
362 value: shell.orientationAngle
365 target: applicationsDisplayLoader.item
366 property: "shellOrientation"
367 value: shell.orientation
370 target: applicationsDisplayLoader.item
371 property: "orientations"
372 value: shell.orientations
375 target: applicationsDisplayLoader.item
376 property: "background"
377 value: wallpaperResolver.cachedBackground
380 target: applicationsDisplayLoader.item
381 property: "nativeWidth"
382 value: shell.nativeWidth
385 target: applicationsDisplayLoader.item
386 property: "nativeHeight"
387 value: shell.nativeHeight
390 target: applicationsDisplayLoader.item
391 property: "beingResized"
392 value: shell.beingResized
395 target: applicationsDisplayLoader.item
396 property: "keepDashRunning"
397 value: launcher.shown || launcher.dashSwipe
400 target: applicationsDisplayLoader.item
401 property: "suspended"
405 target: applicationsDisplayLoader.item
406 property: "altTabPressed"
407 value: physicalKeysMapper.altTabPressed
410 target: applicationsDisplayLoader.item
411 property: "leftMargin"
412 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
415 target: applicationsDisplayLoader.item
416 property: "applicationManager"
417 value: ApplicationManager
420 target: applicationsDisplayLoader.item
421 property: "topLevelSurfaceList"
422 value: topLevelSurfaceList
425 target: applicationsDisplayLoader.item
426 property: "oskEnabled"
427 value: shell.oskEnabled
434 objectName: "inputMethod"
437 topMargin: panel.panelHeight
438 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
440 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
446 anchors.topMargin: panel.panelHeight
447 sourceComponent: shell.mode != "shell" ? integratedGreeter :
448 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
450 item.objectName = "greeter"
455 id: integratedGreeter
458 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
459 hides: [launcher, panel.indicators]
460 tabletMode: shell.usageScenario != "phone"
461 launcherOffset: launcher.progress
462 forcedUnlock: wizard.active || shell.mode === "full-shell"
463 background: wallpaperResolver.cachedBackground
464 hasCustomBackground: wallpaperResolver.hasCustomBackground
465 allowFingerprint: !dialogs.hasActiveDialog &&
466 !notifications.topmostIsFullscreen &&
467 !panel.indicators.shown
469 // avoid overlapping with Launcher's edge drag area
470 // FIXME: Fix TouchRegistry & friends and remove this workaround
471 // Issue involves launcher's DDA getting disabled on a long
473 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
480 if (!tutorial.running) {
485 onEmergencyCall: startLockedApp("dialer-app")
490 // See powerConnection for why this is useful
491 id: showGreeterDelayed
503 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
504 // We just received an incoming call while locked. The
505 // indicator will have already launched dialer-app for us, but
506 // there is a race between "hasCalls" changing and the dialer
507 // starting up. So in case we lose that race, we'll start/
508 // focus the dialer ourselves here too. Even if the indicator
509 // didn't launch the dialer for some reason (or maybe a call
510 // started via some other means), if an active call is
511 // happening, we want to be in the dialer.
512 startLockedApp("dialer-app")
522 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
523 !callManager.hasCalls && !wizard.active) {
524 // We don't want to simply call greeter.showNow() here, because
525 // that will take too long. Qt will delay button event
526 // handling until the greeter is done loading and may think the
527 // user held down the power button the whole time, leading to a
528 // power dialog being shown. Instead, delay showing the
529 // greeter until we've finished handling the event. We could
530 // make the greeter load asynchronously instead, but that
531 // introduces a whole host of timing issues, especially with
532 // its animations. So this is simpler.
533 showGreeterDelayed.start();
538 function showHome() {
539 greeter.notifyUserRequestedApp();
541 if (shell.mode === "greeter") {
542 SessionBroadcast.requestHomeShown(AccountsService.user);
544 var animate = !LightDMService.greeter.active && !stages.shown;
545 dash.setCurrentScope(0, animate, false);
546 ApplicationManager.requestFocusApplication("unity8-dash");
550 function showDash() {
551 if (greeter.notifyShowingDashFromDrag()) {
555 if (!greeter.locked && tutorial.launcherLongSwipeEnabled
556 && ApplicationManager.focusedApplicationId != "unity8-dash") {
557 ApplicationManager.requestFocusApplication("unity8-dash")
571 anchors.fill: parent //because this draws indicator menus
574 available: tutorial.panelEnabled
575 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
576 && (!greeter || !greeter.hasLockedApp)
577 && !shell.waitingOnGreeter
578 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
580 minimizedPanelHeight: units.gu(3)
581 expandedPanelHeight: units.gu(7)
583 indicatorsModel: Indicators.IndicatorsModel {
584 // tablet and phone both use the same profile
585 // FIXME: use just "phone" for greeter too, but first fix
586 // greeter app launching to either load the app inside the
587 // greeter or tell the session to load the app. This will
588 // involve taking the url-dispatcher dbus name and using
589 // SessionBroadcast to tell the session.
590 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
591 Component.onCompleted: load();
596 greeterShown: greeter.shown
599 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
600 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
602 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
603 || greeter.hasLockedApp
604 locked: greeter && greeter.active
609 objectName: "launcher"
612 * Since the Dash doesn't have the same controll over surfaces that the
613 * Shell does, it can't slowly move the scope out of the way, as the shell
614 * does with apps, and the dash is show instantly. This allows for some
615 * leeway and prevents accidental home swipes.
617 readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
618 readonly property bool dashSwipe: progress > offset
620 anchors.top: parent.top
621 anchors.topMargin: inverted ? 0 : panel.panelHeight
622 anchors.bottom: parent.bottom
624 dragAreaWidth: shell.edgeSize
625 available: tutorial.launcherEnabled
626 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
627 && !greeter.hasLockedApp
628 && !shell.waitingOnGreeter
629 inverted: shell.usageScenario !== "desktop"
630 superPressed: physicalKeysMapper.superPressed
631 superTabPressed: physicalKeysMapper.superTabPressed
632 panelWidth: units.gu(settings.launcherWidth)
633 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
635 onShowDashHome: showHome()
637 onDashSwipeChanged: {
639 dash.setCurrentScope(0, false, true)
642 onLauncherApplicationSelected: {
643 greeter.notifyUserRequestedApp();
644 shell.activateApplication(appId);
648 panel.indicators.hide()
653 applicationsDisplayLoader.focus = true;
658 shortcut: Qt.AltModifier | Qt.Key_F1
660 launcher.openForKeyboardNavigation();
664 shortcut: Qt.MetaModifier | Qt.Key_0
666 if (LauncherModel.get(9)) {
667 activateApplication(LauncherModel.get(9).appId);
674 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
676 if (LauncherModel.get(index)) {
677 activateApplication(LauncherModel.get(index).appId);
684 KeyboardShortcutsOverlay {
685 objectName: "shortcutsOverlay"
686 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
687 && height < parent.height - padding - panel.panelHeight
688 anchors.centerIn: parent
689 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
690 anchors.verticalCenterOffset: panel.panelHeight/2
692 opacity: enabled ? 0.95 : 0
694 Behavior on opacity {
695 UbuntuNumberAnimation {}
701 objectName: "tutorial"
704 paused: callManager.hasCalls || !greeter || greeter.active ||
706 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
707 inputMethod.visible ||
708 (launcher.shown && !launcher.lockedVisible) ||
709 panel.indicators.shown || stage.dragProgress > 0
710 usageScenario: shell.usageScenario
711 lastInputTimestamp: inputFilter.lastInputTimestamp
714 stage: applicationsDisplayLoader.item
721 deferred: shell.mode === "greeter"
723 function unlockWhenDoneWithWizard() {
725 Connectivity.unlockAllModems();
729 Component.onCompleted: unlockWhenDoneWithWizard()
730 onActiveChanged: unlockWhenDoneWithWizard()
733 MouseArea { // modal notifications prevent interacting with other contents
735 visible: notifications.useModal
742 model: NotificationBackend.Model
744 hasMouse: shell.hasMouse
745 background: wallpaperResolver.cachedBackground
747 y: topmostIsFullscreen ? 0 : panel.panelHeight
748 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
753 when: overlay.width <= units.gu(60)
755 target: notifications
756 anchors.left: parent.left
757 anchors.right: parent.right
762 when: overlay.width > units.gu(60)
764 target: notifications
765 anchors.left: undefined
766 anchors.right: parent.right
768 PropertyChanges { target: notifications; width: units.gu(38) }
776 objectName: "dialogs"
779 usageScenario: shell.usageScenario
781 shutdownFadeOutRectangle.enabled = true;
782 shutdownFadeOutRectangle.visible = true;
783 shutdownFadeOut.start();
788 target: SessionBroadcast
789 onShowHome: if (shell.mode !== "greeter") showHome()
794 objectName: "urlDispatcher"
795 active: shell.mode === "greeter"
796 onUrlRequested: shell.activateURL(url)
803 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
805 target: applicationsDisplayLoader.item
806 ignoreUnknownSignals: true
807 onItemSnapshotRequested: itemGrabber.capture(item)
813 visible: shell.hasMouse
815 topBoundaryOffset: panel.panelHeight
817 confiningItem: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.itemConfiningMouseCursor : null
819 property bool mouseNeverMoved: true
821 target: cursor; property: "x"; value: shell.width / 2
822 when: cursor.mouseNeverMoved && cursor.visible
825 target: cursor; property: "y"; value: shell.height / 2
826 when: cursor.mouseNeverMoved && cursor.visible
831 readonly property var previewRectangle: applicationsDisplayLoader.item && applicationsDisplayLoader.item.previewRectangle &&
832 applicationsDisplayLoader.item.previewRectangle.target &&
833 applicationsDisplayLoader.item.previewRectangle.target.dragging ?
834 applicationsDisplayLoader.item.previewRectangle : null
836 onPushedLeftBoundary: {
837 if (buttons === Qt.NoButton) {
838 launcher.pushEdge(amount);
839 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
840 previewRectangle.maximizeLeft(amount);
844 onPushedRightBoundary: {
845 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
846 && applicationsDisplayLoader.item.pushRightEdge) {
847 applicationsDisplayLoader.item.pushRightEdge(amount);
848 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
849 previewRectangle.maximizeRight(amount);
853 onPushedTopBoundary: {
854 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
855 previewRectangle.maximize(amount);
858 onPushedTopLeftCorner: {
859 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
860 previewRectangle.maximizeTopLeft(amount);
863 onPushedTopRightCorner: {
864 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
865 previewRectangle.maximizeTopRight(amount);
868 onPushedBottomLeftCorner: {
869 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
870 previewRectangle.maximizeBottomLeft(amount);
873 onPushedBottomRightCorner: {
874 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
875 previewRectangle.maximizeBottomRight(amount);
879 if (previewRectangle) {
880 previewRectangle.stop();
885 mouseNeverMoved = false;
894 id: shutdownFadeOutRectangle
901 NumberAnimation on opacity {
906 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
907 DBusUnitySessionService.shutdown();