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 1.0
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 panelAreaShowProgress: panel.panelAreaShowProgress
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();
67 function updateFocusedAppOrientationAnimated() {
68 stage.updateFocusedAppOrientationAnimated();
70 property bool hasMouse: false
71 property bool hasKeyboard: false
72 property bool hasTouchscreen: false
74 // to be read from outside
75 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
77 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
78 && stage.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;
93 return shell.orientations.map(stage.supportedOrientations);
97 readonly property var mainApp: stage.mainApp
101 _onMainAppChanged(mainApp.appId);
105 target: ApplicationManager
107 if (shell.mainApp && shell.mainApp.appId === appId) {
108 _onMainAppChanged(appId);
112 function _onMainAppChanged(appId) {
113 if (wizard.active && appId != "") {
114 // If this happens on first boot, we may be in the
115 // wizard while receiving a call. But a call is more
116 // important than the wizard so just bail out of it.
120 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
121 // If we are in the middle of a call, make dialer lockedApp and show it.
122 // This can happen if user backs out of dialer back to greeter, then
123 // launches dialer again.
124 greeter.lockedApp = appId;
126 greeter.notifyAppFocusRequested(appId);
128 panel.indicators.hide();
129 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
132 // For autopilot consumption
133 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
135 // Note when greeter is waiting on PAM, so that we can disable edges until
136 // we know which user data to show and whether the session is locked.
137 readonly property bool waitingOnGreeter: greeter && greeter.waiting
139 property real edgeSize: units.gu(settings.edgeDragWidth)
142 id: wallpaperResolver
143 objectName: "wallpaperResolver"
145 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
146 readonly property bool hasCustomBackground: background != defaultBackground
148 // Use a cached version of the scaled-down wallpaper (as sometimes the
149 // image can be quite big compared to the device size, including for
150 // our default wallpaper). We use a name=wallpaper argument here to
151 // make sure we don't litter our cache with lots of scaled images. We
152 // only need to bother caching one at a time.
153 readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
156 id: backgroundSettings
157 schema.id: "org.gnome.desktop.background"
161 AccountsService.backgroundFile,
162 backgroundSettings.pictureUri,
167 readonly property alias greeter: greeterLoader.item
169 function activateApplication(appId) {
170 // Either open the app in our own session, or -- if we're acting as a
171 // greeter -- ask the user's session to open it for us.
172 if (shell.mode === "greeter") {
173 activateURL("application:///" + appId + ".desktop");
179 function activateURL(url) {
180 SessionBroadcast.requestUrlStart(AccountsService.user, url);
181 greeter.notifyUserRequestedApp();
182 panel.indicators.hide();
185 function startApp(appId) {
186 if (ApplicationManager.findApplication(appId)) {
187 ApplicationManager.requestFocusApplication(appId);
189 ApplicationManager.startApplication(appId);
193 function startLockedApp(app) {
194 if (greeter.locked) {
195 greeter.lockedApp = app;
197 startApp(app); // locked apps are always in our same session
201 target: LauncherModel
202 property: "applicationManager"
203 value: ApplicationManager
206 Component.onCompleted: {
207 finishStartUpTimer.start();
216 objectName: "dashCommunicator"
220 id: physicalKeysMapper
221 objectName: "physicalKeysMapper"
223 onPowerKeyLongPressed: dialogs.showPowerDialog();
224 onVolumeDownTriggered: volumeControl.volumeDown();
225 onVolumeUpTriggered: volumeControl.volumeUp();
226 onScreenshotTriggered: itemGrabber.capture(shell);
230 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
235 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
236 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
240 objectName: "windowInputMonitor"
241 onHomeKeyActivated: {
242 // Ignore when greeter is active, to avoid pocket presses
243 if (!greeter.active) {
244 launcher.openDrawer(false);
247 onTouchBegun: { cursor.opacity = 0; }
249 // move the (hidden) cursor to the last known touch position
250 var mappedCoords = mapFromItem(null, pos.x, pos.y);
251 cursor.x = mappedCoords.x;
252 cursor.y = mappedCoords.y;
253 cursor.mouseNeverMoved = false;
257 AvailableDesktopArea {
258 id: availableDesktopAreaItem
260 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
261 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
266 schema.id: "com.canonical.Unity8"
273 height: parent.height
277 objectName: "surfaceManager"
279 TopLevelWindowModel {
280 id: topLevelSurfaceList
281 objectName: "topLevelSurfaceList"
282 applicationManager: ApplicationManager // it's a singleton
283 surfaceManager: surfaceMan
292 dragAreaWidth: shell.edgeSize
293 background: wallpaperResolver.background
295 applicationManager: ApplicationManager
296 topLevelSurfaceList: topLevelSurfaceList
297 inputMethodRect: inputMethod.visibleRect
298 rightEdgePushProgress: rightEdgeBarrier.progress
299 availableDesktopArea: availableDesktopAreaItem
301 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
303 : shell.usageScenario
305 mode: usageScenario == "phone" ? "staged"
306 : usageScenario == "tablet" ? "stagedWithSideStage"
309 shellOrientation: shell.orientation
310 shellOrientationAngle: shell.orientationAngle
311 orientations: shell.orientations
312 nativeWidth: shell.nativeWidth
313 nativeHeight: shell.nativeHeight
315 interactive: (!greeter || !greeter.shown)
316 && panel.indicators.fullyClosed
317 && !notifications.useModal
319 onInteractiveChanged: { if (interactive) { focus = true; } }
321 suspended: greeter.shown
322 altTabPressed: physicalKeysMapper.altTabPressed
323 oskEnabled: shell.oskEnabled
324 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
326 onSpreadShownChanged: {
327 panel.indicators.hide();
328 panel.applicationMenus.hide();
335 minimumTouchPoints: 4
336 maximumTouchPoints: minimumTouchPoints
338 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
339 touchPoints.length >= minimumTouchPoints &&
340 touchPoints.length <= maximumTouchPoints
341 property bool wasPressed: false
343 onRecognisedPressChanged: {
344 if (recognisedPress) {
350 if (status !== TouchGestureArea.Recognized) {
351 if (status === TouchGestureArea.WaitingForTouch) {
352 if (wasPressed && !dragging) {
353 launcher.openDrawer(true);
364 objectName: "inputMethod"
365 surface: topLevelSurfaceList.inputMethodSurface
368 topMargin: panel.panelHeight
369 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
371 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
376 objectName: "greeterLoader"
378 anchors.topMargin: panel.panelHeight
379 sourceComponent: shell.mode != "shell" ? integratedGreeter :
380 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
382 item.objectName = "greeter"
384 property bool openDrawerAfterUnlock: false
388 if (!greeter.active && greeterLoader.openDrawerAfterUnlock) {
389 launcher.openDrawer(false);
390 greeterLoader.openDrawerAfterUnlock = false;
397 id: integratedGreeter
400 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
401 hides: [launcher, panel.indicators, panel.applicationMenus]
402 tabletMode: shell.usageScenario != "phone"
403 forcedUnlock: wizard.active || shell.mode === "full-shell"
404 background: wallpaperResolver.cachedBackground
405 hasCustomBackground: wallpaperResolver.hasCustomBackground
406 allowFingerprint: !dialogs.hasActiveDialog &&
407 !notifications.topmostIsFullscreen &&
408 !panel.indicators.shown
410 // avoid overlapping with Launcher's edge drag area
411 // FIXME: Fix TouchRegistry & friends and remove this workaround
412 // Issue involves launcher's DDA getting disabled on a long
414 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
421 if (!tutorial.running) {
426 onEmergencyCall: startLockedApp("dialer-app")
431 // See powerConnection for why this is useful
432 id: showGreeterDelayed
435 // Go through the dbus service, because it has checks for whether
436 // we are even allowed to lock or not.
437 DBusUnitySessionService.PromptLock();
446 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
447 // We just received an incoming call while locked. The
448 // indicator will have already launched dialer-app for us, but
449 // there is a race between "hasCalls" changing and the dialer
450 // starting up. So in case we lose that race, we'll start/
451 // focus the dialer ourselves here too. Even if the indicator
452 // didn't launch the dialer for some reason (or maybe a call
453 // started via some other means), if an active call is
454 // happening, we want to be in the dialer.
455 startLockedApp("dialer-app")
465 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
466 !callManager.hasCalls && !wizard.active) {
467 // We don't want to simply call greeter.showNow() here, because
468 // that will take too long. Qt will delay button event
469 // handling until the greeter is done loading and may think the
470 // user held down the power button the whole time, leading to a
471 // power dialog being shown. Instead, delay showing the
472 // greeter until we've finished handling the event. We could
473 // make the greeter load asynchronously instead, but that
474 // introduces a whole host of timing issues, especially with
475 // its animations. So this is simpler.
476 showGreeterDelayed.start();
481 function showHome() {
482 greeter.notifyUserRequestedApp();
484 if (shell.mode === "greeter") {
485 SessionBroadcast.requestHomeShown(AccountsService.user);
487 if (!greeter.active) {
488 launcher.openDrawer(false);
490 greeterLoader.openDrawerAfterUnlock = true;
504 anchors.fill: parent //because this draws indicator menus
506 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
507 minimizedPanelHeight: units.gu(3)
508 expandedPanelHeight: units.gu(7)
509 indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
510 applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
511 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
515 available: tutorial.panelEnabled
516 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
517 && (!greeter || !greeter.hasLockedApp)
518 && !shell.waitingOnGreeter
519 && settings.enableIndicatorMenu
521 model: Indicators.IndicatorsModel {
522 // tablet and phone both use the same profile
523 // FIXME: use just "phone" for greeter too, but first fix
524 // greeter app launching to either load the app inside the
525 // greeter or tell the session to load the app. This will
526 // involve taking the url-dispatcher dbus name and using
527 // SessionBroadcast to tell the session.
528 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
529 Component.onCompleted: load();
535 available: (!greeter || !greeter.shown)
536 && !shell.waitingOnGreeter
537 && !stage.spreadShown
540 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
541 ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
543 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
544 || greeter.hasLockedApp
545 greeterShown: greeter && greeter.shown
546 hasKeyboard: shell.hasKeyboard
551 objectName: "launcher"
553 anchors.top: parent.top
554 anchors.topMargin: inverted ? 0 : panel.panelHeight
555 anchors.bottom: parent.bottom
557 dragAreaWidth: shell.edgeSize
558 available: tutorial.launcherEnabled
559 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
560 && !greeter.hasLockedApp
561 && !shell.waitingOnGreeter
562 && settings.enableLauncher
563 inverted: shell.usageScenario !== "desktop"
564 superPressed: physicalKeysMapper.superPressed
565 superTabPressed: physicalKeysMapper.superTabPressed
566 panelWidth: units.gu(settings.launcherWidth)
567 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
568 blurSource: greeter.shown ? greeter : stages
569 topPanelHeight: panel.panelHeight
570 drawerEnabled: !greeter.active
571 privateMode: greeter.active
573 onShowDashHome: showHome()
574 onLauncherApplicationSelected: {
575 greeter.notifyUserRequestedApp();
576 shell.activateApplication(appId);
580 panel.indicators.hide();
581 panel.applicationMenus.hide();
584 onDrawerShownChanged: {
586 panel.indicators.hide();
587 panel.applicationMenus.hide();
597 shortcut: Qt.MetaModifier | Qt.Key_A
599 launcher.openDrawer(true);
603 shortcut: Qt.AltModifier | Qt.Key_F1
605 launcher.openForKeyboardNavigation();
609 shortcut: Qt.MetaModifier | Qt.Key_0
611 if (LauncherModel.get(9)) {
612 activateApplication(LauncherModel.get(9).appId);
619 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
621 if (LauncherModel.get(index)) {
622 activateApplication(LauncherModel.get(index).appId);
629 KeyboardShortcutsOverlay {
630 objectName: "shortcutsOverlay"
631 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
632 && height < parent.height - padding - panel.panelHeight
633 anchors.centerIn: parent
634 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
635 anchors.verticalCenterOffset: panel.panelHeight/2
637 opacity: enabled ? 0.95 : 0
639 Behavior on opacity {
640 UbuntuNumberAnimation {}
646 objectName: "tutorial"
649 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
650 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
651 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
652 inputMethod.visible ||
653 (launcher.shown && !launcher.lockedVisible) ||
654 panel.indicators.shown || stage.rightEdgeDragProgress > 0
655 usageScenario: shell.usageScenario
656 lastInputTimestamp: inputFilter.lastInputTimestamp
666 deferred: shell.mode === "greeter"
668 function unlockWhenDoneWithWizard() {
670 Connectivity.unlockAllModems();
674 Component.onCompleted: unlockWhenDoneWithWizard()
675 onActiveChanged: unlockWhenDoneWithWizard()
678 MouseArea { // modal notifications prevent interacting with other contents
680 visible: notifications.useModal
687 model: NotificationBackend.Model
689 hasMouse: shell.hasMouse
690 background: wallpaperResolver.cachedBackground
692 y: topmostIsFullscreen ? 0 : panel.panelHeight
693 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
698 when: overlay.width <= units.gu(60)
700 target: notifications
701 anchors.left: parent.left
702 anchors.right: parent.right
707 when: overlay.width > units.gu(60)
709 target: notifications
710 anchors.left: undefined
711 anchors.right: parent.right
713 PropertyChanges { target: notifications; width: units.gu(38) }
720 enabled: !greeter.shown
722 // NB: it does its own positioning according to the specified edge
726 panel.indicators.hide()
729 material: Component {
735 anchors.centerIn: parent
737 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
738 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
748 objectName: "dialogs"
750 visible: hasActiveDialog
752 usageScenario: shell.usageScenario
753 hasKeyboard: shell.hasKeyboard
755 shutdownFadeOutRectangle.enabled = true;
756 shutdownFadeOutRectangle.visible = true;
757 shutdownFadeOut.start();
762 target: SessionBroadcast
763 onShowHome: if (shell.mode !== "greeter") showHome()
768 objectName: "urlDispatcher"
769 active: shell.mode === "greeter"
770 onUrlRequested: shell.activateURL(url)
777 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
780 ignoreUnknownSignals: true
781 onItemSnapshotRequested: itemGrabber.capture(item)
786 id: cursorHidingTimer
788 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
789 onTriggered: cursor.opacity = 0;
795 visible: shell.hasMouse
797 topBoundaryOffset: panel.panelHeight
799 confiningItem: stage.itemConfiningMouseCursor
801 property bool mouseNeverMoved: true
803 target: cursor; property: "x"; value: shell.width / 2
804 when: cursor.mouseNeverMoved && cursor.visible
807 target: cursor; property: "y"; value: shell.height / 2
808 when: cursor.mouseNeverMoved && cursor.visible
813 readonly property var previewRectangle: stage.previewRectangle.target &&
814 stage.previewRectangle.target.dragging ?
815 stage.previewRectangle : null
817 onPushedLeftBoundary: {
818 if (buttons === Qt.NoButton) {
819 launcher.pushEdge(amount);
820 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
821 previewRectangle.maximizeLeft(amount);
825 onPushedRightBoundary: {
826 if (buttons === Qt.NoButton) {
827 rightEdgeBarrier.push(amount);
828 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
829 previewRectangle.maximizeRight(amount);
833 onPushedTopBoundary: {
834 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
835 previewRectangle.maximize(amount);
838 onPushedTopLeftCorner: {
839 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
840 previewRectangle.maximizeTopLeft(amount);
843 onPushedTopRightCorner: {
844 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
845 previewRectangle.maximizeTopRight(amount);
848 onPushedBottomLeftCorner: {
849 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
850 previewRectangle.maximizeBottomLeft(amount);
853 onPushedBottomRightCorner: {
854 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
855 previewRectangle.maximizeBottomRight(amount);
859 if (previewRectangle) {
860 previewRectangle.stop();
865 mouseNeverMoved = false;
869 Behavior on opacity { UbuntuNumberAnimation {} }
872 // non-visual objects
874 focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
879 id: shutdownFadeOutRectangle
886 NumberAnimation on opacity {
891 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
892 DBusUnitySessionService.shutdown();