2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2019-2021 UBports Foundation
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import QtQuick.Window 2.2
20 import AccountsService 0.1
21 import Unity.Application 0.1
22 import Ubuntu.Components 1.3
23 import Ubuntu.Components.Popups 1.3
24 import Ubuntu.Gestures 0.1
25 import Ubuntu.Telephony 0.1 as Telephony
26 import Unity.Connectivity 0.1
27 import Unity.Launcher 0.1
28 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.Indicators 0.1 as Indicators
45 import WindowManager 1.0
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 panelAreaShowProgress: panel.panelAreaShowProgress
60 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
61 property string mode: "full-greeter"
62 property bool interactiveBlur: false
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
73 property bool supportsMultiColorLed: true
75 // The largest dimension, in pixels, of all of the screens this Shell is
77 // If a script sets the shell to 240x320 when it was 320x240, we could
78 // end up in a situation where our dimensions are 240x240 for a short time.
79 // Notifying the Wallpaper of both events would make it reload the image
80 // twice. So, we use a Binding { delayed: true }.
81 property real largestScreenDimension
85 property: "largestScreenDimension"
86 value: Math.max(nativeWidth, nativeHeight)
90 property alias lightIndicators: indicatorsModel.light
92 // to be read from outside
93 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
95 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
96 && stage.orientationChangesEnabled
97 && (!greeter.animating)
99 readonly property bool showingGreeter: greeter && greeter.shown
101 property bool startingUp: true
102 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
104 property int supportedOrientations: {
106 // Ensure we don't rotate during start up
107 return Qt.PrimaryOrientation;
108 } else if (notifications.topmostIsFullscreen) {
109 return Qt.PrimaryOrientation;
111 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
115 readonly property var mainApp: stage.mainApp
118 _onMainAppChanged((mainApp ? mainApp.appId : ""));
121 target: ApplicationManager
123 if (shell.mainApp && shell.mainApp.appId === appId) {
124 _onMainAppChanged(appId);
129 // Calls attention back to the most important thing that's been focused
130 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
131 // goes over everything if it is locked)
132 // Must be called whenever app focus changes occur, even if the focus change
133 // is "nothing is focused". In that case, call with appId = ""
134 function _onMainAppChanged(appId) {
138 // If this happens on first boot, we may be in the
139 // wizard while receiving a call. A call is more
140 // important than the wizard so just bail out of it.
144 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
145 // If we are in the middle of a call, make dialer lockedApp. The
146 // Greeter will show it when it's notified of the focus.
147 // This can happen if user backs out of dialer back to greeter, then
148 // launches dialer again.
149 greeter.lockedApp = appId;
152 panel.indicators.hide();
153 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
156 // *Always* make sure the greeter knows that the focused app changed
157 if (greeter) greeter.notifyAppFocusRequested(appId);
160 // For autopilot consumption
161 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
163 // Note when greeter is waiting on PAM, so that we can disable edges until
164 // we know which user data to show and whether the session is locked.
165 readonly property bool waitingOnGreeter: greeter && greeter.waiting
167 // True when the user is logged in with no apps running
168 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
170 onAtDesktopChanged: {
171 if (atDesktop && stage) {
176 property real edgeSize: units.gu(settings.edgeDragWidth)
179 id: wallpaperResolver
180 objectName: "wallpaperResolver"
182 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
183 readonly property bool hasCustomBackground: background != defaultBackground
186 id: backgroundSettings
187 schema.id: "org.gnome.desktop.background"
191 AccountsService.backgroundFile,
192 backgroundSettings.pictureUri,
197 readonly property alias greeter: greeterLoader.item
199 function activateApplication(appId) {
200 topLevelSurfaceList.pendingActivation();
202 // Either open the app in our own session, or -- if we're acting as a
203 // greeter -- ask the user's session to open it for us.
204 if (shell.mode === "greeter") {
205 activateURL("application:///" + appId + ".desktop");
212 function activateURL(url) {
213 SessionBroadcast.requestUrlStart(AccountsService.user, url);
214 greeter.notifyUserRequestedApp();
215 panel.indicators.hide();
218 function startApp(appId) {
219 if (ApplicationManager.findApplication(appId)) {
220 ApplicationManager.requestFocusApplication(appId);
222 ApplicationManager.startApplication(appId);
226 function startLockedApp(app) {
227 topLevelSurfaceList.pendingActivation();
229 if (greeter.locked) {
230 greeter.lockedApp = app;
232 startApp(app); // locked apps are always in our same session
236 target: LauncherModel
237 property: "applicationManager"
238 value: ApplicationManager
241 Component.onCompleted: {
242 finishStartUpTimer.start();
250 id: physicalKeysMapper
251 objectName: "physicalKeysMapper"
253 onPowerKeyLongPressed: dialogs.showPowerDialog();
254 onVolumeDownTriggered: volumeControl.volumeDown();
255 onVolumeUpTriggered: volumeControl.volumeUp();
256 onScreenshotTriggered: itemGrabber.capture(shell);
260 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
265 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
266 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
270 objectName: "windowInputMonitor"
271 onHomeKeyActivated: {
272 // Ignore when greeter is active, to avoid pocket presses
273 if (!greeter.active) {
274 launcher.toggleDrawer(/* focusInputField */ false,
275 /* onlyOpen */ false,
276 /* alsoToggleLauncher */ true);
279 onTouchBegun: { cursor.opacity = 0; }
281 // move the (hidden) cursor to the last known touch position
282 var mappedCoords = mapFromItem(null, pos.x, pos.y);
283 cursor.x = mappedCoords.x;
284 cursor.y = mappedCoords.y;
285 cursor.mouseNeverMoved = false;
289 AvailableDesktopArea {
290 id: availableDesktopAreaItem
292 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
293 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
298 schema.id: "com.canonical.Unity8"
305 height: parent.height
309 objectName: "surfaceManager"
311 TopLevelWindowModel {
312 id: topLevelSurfaceList
313 objectName: "topLevelSurfaceList"
314 applicationManager: ApplicationManager // it's a singleton
315 surfaceManager: surfaceMan
324 dragAreaWidth: shell.edgeSize
325 background: wallpaperResolver.background
326 backgroundSourceSize: shell.largestScreenDimension
328 applicationManager: ApplicationManager
329 topLevelSurfaceList: topLevelSurfaceList
330 inputMethodRect: inputMethod.visibleRect
331 rightEdgePushProgress: rightEdgeBarrier.progress
332 availableDesktopArea: availableDesktopAreaItem
333 launcherLeftMargin: launcher.visibleWidth
335 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
337 : shell.usageScenario
339 mode: usageScenario == "phone" ? "staged"
340 : usageScenario == "tablet" ? "stagedWithSideStage"
343 shellOrientation: shell.orientation
344 shellOrientationAngle: shell.orientationAngle
345 orientations: shell.orientations
346 nativeWidth: shell.nativeWidth
347 nativeHeight: shell.nativeHeight
349 allowInteractivity: (!greeter || !greeter.shown)
350 && panel.indicators.fullyClosed
351 && !notifications.useModal
352 && !launcher.takesFocus
354 suspended: greeter.shown
355 altTabPressed: physicalKeysMapper.altTabPressed
356 oskEnabled: shell.oskEnabled
357 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
359 onSpreadShownChanged: {
360 panel.indicators.hide();
361 panel.applicationMenus.hide();
368 minimumTouchPoints: 4
369 maximumTouchPoints: minimumTouchPoints
371 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
372 touchPoints.length >= minimumTouchPoints &&
373 touchPoints.length <= maximumTouchPoints
374 property bool wasPressed: false
376 onRecognisedPressChanged: {
377 if (recognisedPress) {
383 if (status !== TouchGestureArea.Recognized) {
384 if (status === TouchGestureArea.WaitingForTouch) {
385 if (wasPressed && !dragging) {
386 launcher.toggleDrawer(true);
397 objectName: "inputMethod"
400 topMargin: panel.panelHeight
401 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
403 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
408 objectName: "greeterLoader"
410 sourceComponent: shell.mode != "shell" ? integratedGreeter :
411 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
413 item.objectName = "greeter"
415 property bool toggleDrawerAfterUnlock: false
422 // Show drawer in case showHome() requests it
423 if (greeterLoader.toggleDrawerAfterUnlock) {
424 launcher.toggleDrawer(false);
425 greeterLoader.toggleDrawerAfterUnlock = false;
434 id: integratedGreeter
437 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
438 hides: [launcher, panel.indicators, panel.applicationMenus]
439 tabletMode: shell.usageScenario != "phone"
440 usageMode: shell.usageScenario
441 orientation: shell.orientation
442 forcedUnlock: wizard.active || shell.mode === "full-shell"
443 background: wallpaperResolver.background
444 backgroundSourceSize: shell.largestScreenDimension
445 hasCustomBackground: wallpaperResolver.hasCustomBackground
446 inputMethodRect: inputMethod.visibleRect
447 hasKeyboard: shell.hasKeyboard
448 allowFingerprint: !dialogs.hasActiveDialog &&
449 !notifications.topmostIsFullscreen &&
450 !panel.indicators.shown
451 panelHeight: panel.panelHeight
453 // avoid overlapping with Launcher's edge drag area
454 // FIXME: Fix TouchRegistry & friends and remove this workaround
455 // Issue involves launcher's DDA getting disabled on a long
457 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
460 if (!tutorial.running) {
465 onEmergencyCall: startLockedApp("dialer-app")
470 // See powerConnection for why this is useful
471 id: showGreeterDelayed
474 // Go through the dbus service, because it has checks for whether
475 // we are even allowed to lock or not.
476 DBusUnitySessionService.PromptLock();
485 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
486 // We just received an incoming call while locked. The
487 // indicator will have already launched dialer-app for us, but
488 // there is a race between "hasCalls" changing and the dialer
489 // starting up. So in case we lose that race, we'll start/
490 // focus the dialer ourselves here too. Even if the indicator
491 // didn't launch the dialer for some reason (or maybe a call
492 // started via some other means), if an active call is
493 // happening, we want to be in the dialer.
494 startLockedApp("dialer-app")
504 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
505 !callManager.hasCalls && !wizard.active) {
506 // We don't want to simply call greeter.showNow() here, because
507 // that will take too long. Qt will delay button event
508 // handling until the greeter is done loading and may think the
509 // user held down the power button the whole time, leading to a
510 // power dialog being shown. Instead, delay showing the
511 // greeter until we've finished handling the event. We could
512 // make the greeter load asynchronously instead, but that
513 // introduces a whole host of timing issues, especially with
514 // its animations. So this is simpler.
515 showGreeterDelayed.start();
520 function showHome() {
521 greeter.notifyUserRequestedApp();
523 if (shell.mode === "greeter") {
524 SessionBroadcast.requestHomeShown(AccountsService.user);
526 if (!greeter.active) {
527 launcher.toggleDrawer(false);
529 greeterLoader.toggleDrawerAfterUnlock = true;
543 anchors.fill: parent //because this draws indicator menus
545 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
546 minimizedPanelHeight: units.gu(3)
547 expandedPanelHeight: units.gu(7)
548 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
552 available: tutorial.panelEnabled
553 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
554 && (!greeter || !greeter.hasLockedApp)
555 && !shell.waitingOnGreeter
556 && settings.enableIndicatorMenu
558 model: Indicators.IndicatorsModel {
560 // tablet and phone both use the same profile
561 // FIXME: use just "phone" for greeter too, but first fix
562 // greeter app launching to either load the app inside the
563 // greeter or tell the session to load the app. This will
564 // involve taking the url-dispatcher dbus name and using
565 // SessionBroadcast to tell the session.
566 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
567 Component.onCompleted: {
575 available: (!greeter || !greeter.shown)
576 && !shell.waitingOnGreeter
577 && !stage.spreadShown
580 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
581 ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
583 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
584 || greeter.hasLockedApp
585 greeterShown: greeter && greeter.shown
586 hasKeyboard: shell.hasKeyboard
587 supportsMultiColorLed: shell.supportsMultiColorLed
592 objectName: "launcher"
594 anchors.top: parent.top
595 anchors.topMargin: inverted ? 0 : panel.panelHeight
596 anchors.bottom: parent.bottom
598 dragAreaWidth: shell.edgeSize
599 available: tutorial.launcherEnabled
600 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
601 && !greeter.hasLockedApp
602 && !shell.waitingOnGreeter
603 inverted: shell.usageScenario !== "desktop"
604 superPressed: physicalKeysMapper.superPressed
605 superTabPressed: physicalKeysMapper.superTabPressed
606 panelWidth: units.gu(settings.launcherWidth)
607 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
608 blurSource: greeter.shown ? greeter : stages
609 interactiveBlur: shell.interactiveBlur
610 topPanelHeight: panel.panelHeight
611 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
612 privateMode: greeter.active
613 background: wallpaperResolver.background
614 backgroundSourceSize: shell.largestScreenDimension
616 // It can be assumed that the Launcher and Panel would overlap if
617 // the Panel is open and taking up the full width of the shell
618 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
620 // The "autohideLauncher" setting is only valid in desktop mode
621 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
623 // The Launcher should absolutely not be locked visible under some
625 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
627 onShowDashHome: showHome()
628 onLauncherApplicationSelected: {
629 greeter.notifyUserRequestedApp();
630 shell.activateApplication(appId);
634 panel.indicators.hide();
635 panel.applicationMenus.hide();
638 onDrawerShownChanged: {
640 panel.indicators.hide();
641 panel.applicationMenus.hide();
651 shortcut: Qt.MetaModifier | Qt.Key_A
653 launcher.toggleDrawer(true);
657 shortcut: Qt.AltModifier | Qt.Key_F1
659 launcher.openForKeyboardNavigation();
663 shortcut: Qt.MetaModifier | Qt.Key_0
665 if (LauncherModel.get(9)) {
666 activateApplication(LauncherModel.get(9).appId);
673 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
675 if (LauncherModel.get(index)) {
676 activateApplication(LauncherModel.get(index).appId);
683 KeyboardShortcutsOverlay {
684 objectName: "shortcutsOverlay"
685 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
686 && height < parent.height - padding - panel.panelHeight
687 anchors.centerIn: parent
688 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
689 anchors.verticalCenterOffset: panel.panelHeight/2
691 opacity: enabled ? 0.95 : 0
693 Behavior on opacity {
694 UbuntuNumberAnimation {}
700 objectName: "tutorial"
703 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
704 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
705 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
706 inputMethod.visible ||
707 (launcher.shown && !launcher.lockedVisible) ||
708 panel.indicators.shown || stage.rightEdgeDragProgress > 0
709 usageScenario: shell.usageScenario
710 lastInputTimestamp: inputFilter.lastInputTimestamp
720 deferred: shell.mode === "greeter"
722 function unlockWhenDoneWithWizard() {
724 Connectivity.unlockAllModems();
728 Component.onCompleted: unlockWhenDoneWithWizard()
729 onActiveChanged: unlockWhenDoneWithWizard()
732 MouseArea { // modal notifications prevent interacting with other contents
734 visible: notifications.useModal
741 model: NotificationBackend.Model
743 hasMouse: shell.hasMouse
744 background: wallpaperResolver.background
746 y: topmostIsFullscreen ? 0 : panel.panelHeight
747 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
752 when: overlay.width <= units.gu(60)
754 target: notifications
755 anchors.left: parent.left
756 anchors.right: parent.right
761 when: overlay.width > units.gu(60)
763 target: notifications
764 anchors.left: undefined
765 anchors.right: parent.right
767 PropertyChanges { target: notifications; width: units.gu(38) }
774 enabled: !greeter.shown
776 // NB: it does its own positioning according to the specified edge
780 panel.indicators.hide()
783 material: Component {
789 anchors.centerIn: parent
791 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
792 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
802 objectName: "dialogs"
804 visible: hasActiveDialog
806 usageScenario: shell.usageScenario
807 hasKeyboard: shell.hasKeyboard
809 shutdownFadeOutRectangle.enabled = true;
810 shutdownFadeOutRectangle.visible = true;
811 shutdownFadeOut.start();
816 target: SessionBroadcast
817 onShowHome: if (shell.mode !== "greeter") showHome()
822 objectName: "urlDispatcher"
823 active: shell.mode === "greeter"
824 onUrlRequested: shell.activateURL(url)
831 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
834 ignoreUnknownSignals: true
835 onItemSnapshotRequested: itemGrabber.capture(item)
840 id: cursorHidingTimer
842 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
843 onTriggered: cursor.opacity = 0;
849 visible: shell.hasMouse
851 topBoundaryOffset: panel.panelHeight
853 confiningItem: stage.itemConfiningMouseCursor
855 property bool mouseNeverMoved: true
857 target: cursor; property: "x"; value: shell.width / 2
858 when: cursor.mouseNeverMoved && cursor.visible
861 target: cursor; property: "y"; value: shell.height / 2
862 when: cursor.mouseNeverMoved && cursor.visible
867 readonly property var previewRectangle: stage.previewRectangle.target &&
868 stage.previewRectangle.target.dragging ?
869 stage.previewRectangle : null
871 onPushedLeftBoundary: {
872 if (buttons === Qt.NoButton) {
873 launcher.pushEdge(amount);
874 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
875 previewRectangle.maximizeLeft(amount);
879 onPushedRightBoundary: {
880 if (buttons === Qt.NoButton) {
881 rightEdgeBarrier.push(amount);
882 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
883 previewRectangle.maximizeRight(amount);
887 onPushedTopBoundary: {
888 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
889 previewRectangle.maximize(amount);
892 onPushedTopLeftCorner: {
893 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
894 previewRectangle.maximizeTopLeft(amount);
897 onPushedTopRightCorner: {
898 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
899 previewRectangle.maximizeTopRight(amount);
902 onPushedBottomLeftCorner: {
903 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
904 previewRectangle.maximizeBottomLeft(amount);
907 onPushedBottomRightCorner: {
908 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
909 previewRectangle.maximizeBottomRight(amount);
913 if (previewRectangle) {
914 previewRectangle.stop();
919 mouseNeverMoved = false;
923 Behavior on opacity { UbuntuNumberAnimation {} }
926 // non-visual objects
928 focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
933 id: shutdownFadeOutRectangle
940 NumberAnimation on opacity {
945 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
946 DBusUnitySessionService.shutdown();