2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2019-2020 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 "Components/PanelState"
42 import Unity.Notifications 1.0 as NotificationBackend
43 import Unity.Session 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
73 property bool supportsMultiColorLed: true
75 // to be read from outside
76 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
78 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
79 && stage.orientationChangesEnabled
80 && (!greeter || !greeter.animating)
82 readonly property bool showingGreeter: greeter && greeter.shown
84 property bool startingUp: true
85 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
87 property int supportedOrientations: {
89 // Ensure we don't rotate during start up
90 return Qt.PrimaryOrientation;
91 } else if (showingGreeter || notifications.topmostIsFullscreen) {
92 return Qt.PrimaryOrientation;
94 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
98 readonly property var mainApp: stage.mainApp
100 readonly property var topLevelSurfaceList: {
101 if (!WMScreen.currentWorkspace) return null;
102 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
106 _onMainAppChanged((mainApp ? mainApp.appId : ""));
109 target: ApplicationManager
111 if (shell.mainApp && shell.mainApp.appId === appId) {
112 _onMainAppChanged(appId);
117 // Calls attention back to the most important thing that's been focused
118 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
119 // goes over everything if it is locked)
120 // Must be called whenever app focus changes occur, even if the focus change
121 // is "nothing is focused". In that case, call with appId = ""
122 function _onMainAppChanged(appId) {
126 // If this happens on first boot, we may be in the
127 // wizard while receiving a call. A call is more
128 // important than the wizard so just bail out of it.
132 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
133 // If we are in the middle of a call, make dialer lockedApp. The
134 // Greeter will show it when it's notified of the focus.
135 // This can happen if user backs out of dialer back to greeter, then
136 // launches dialer again.
137 greeter.lockedApp = appId;
140 panel.indicators.hide();
141 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
144 // *Always* make sure the greeter knows that the focused app changed
145 if (greeter) greeter.notifyAppFocusRequested(appId);
148 // For autopilot consumption
149 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
151 // Note when greeter is waiting on PAM, so that we can disable edges until
152 // we know which user data to show and whether the session is locked.
153 readonly property bool waitingOnGreeter: greeter && greeter.waiting
155 // True when the user is logged in with no apps running
156 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
158 onAtDesktopChanged: {
159 if (atDesktop && stage) {
164 property real edgeSize: units.gu(settings.edgeDragWidth)
167 id: wallpaperResolver
168 objectName: "wallpaperResolver"
170 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
171 readonly property bool hasCustomBackground: background != defaultBackground
174 id: backgroundSettings
175 schema.id: "org.gnome.desktop.background"
179 AccountsService.backgroundFile,
180 backgroundSettings.pictureUri,
185 readonly property alias greeter: greeterLoader.item
187 function activateApplication(appId) {
188 topLevelSurfaceList.pendingActivation();
190 // Either open the app in our own session, or -- if we're acting as a
191 // greeter -- ask the user's session to open it for us.
192 if (shell.mode === "greeter") {
193 activateURL("application:///" + appId + ".desktop");
200 function activateURL(url) {
201 SessionBroadcast.requestUrlStart(AccountsService.user, url);
202 greeter.notifyUserRequestedApp();
203 panel.indicators.hide();
206 function startApp(appId) {
207 if (!ApplicationManager.findApplication(appId)) {
208 ApplicationManager.startApplication(appId);
210 ApplicationManager.requestFocusApplication(appId);
214 function startLockedApp(app) {
215 topLevelSurfaceList.pendingActivation();
217 if (greeter.locked) {
218 greeter.lockedApp = app;
220 startApp(app); // locked apps are always in our same session
224 target: LauncherModel
225 property: "applicationManager"
226 value: ApplicationManager
229 Component.onCompleted: {
230 finishStartUpTimer.start();
238 id: physicalKeysMapper
239 objectName: "physicalKeysMapper"
241 onPowerKeyLongPressed: dialogs.showPowerDialog();
242 onVolumeDownTriggered: volumeControl.volumeDown();
243 onVolumeUpTriggered: volumeControl.volumeUp();
244 onScreenshotTriggered: itemGrabber.capture(shell);
248 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
253 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
254 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
258 objectName: "windowInputMonitor"
259 onHomeKeyActivated: {
260 // Ignore when greeter is active, to avoid pocket presses
261 if (!greeter.active) {
262 launcher.toggleDrawer(/* focusInputField */ false,
263 /* onlyOpen */ false,
264 /* alsoToggleLauncher */ true);
267 onTouchBegun: { cursor.opacity = 0; }
269 // move the (hidden) cursor to the last known touch position
270 var mappedCoords = mapFromItem(null, pos.x, pos.y);
271 cursor.x = mappedCoords.x;
272 cursor.y = mappedCoords.y;
273 cursor.mouseNeverMoved = false;
277 AvailableDesktopArea {
278 id: availableDesktopAreaItem
280 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
281 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
286 schema.id: "com.canonical.Unity8"
291 objectName: "panelState"
298 height: parent.height
306 dragAreaWidth: shell.edgeSize
307 background: wallpaperResolver.background
309 applicationManager: ApplicationManager
310 topLevelSurfaceList: shell.topLevelSurfaceList
311 inputMethodRect: inputMethod.visibleRect
312 rightEdgePushProgress: rightEdgeBarrier.progress
313 availableDesktopArea: availableDesktopAreaItem
314 launcherLeftMargin: launcher.visibleWidth
316 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
318 : shell.usageScenario
320 mode: usageScenario == "phone" ? "staged"
321 : usageScenario == "tablet" ? "stagedWithSideStage"
324 shellOrientation: shell.orientation
325 shellOrientationAngle: shell.orientationAngle
326 orientations: shell.orientations
327 nativeWidth: shell.nativeWidth
328 nativeHeight: shell.nativeHeight
330 allowInteractivity: (!greeter || !greeter.shown)
331 && panel.indicators.fullyClosed
332 && !notifications.useModal
333 && !launcher.takesFocus
335 suspended: greeter.shown
336 altTabPressed: physicalKeysMapper.altTabPressed
337 oskEnabled: shell.oskEnabled
338 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
339 panelState: panelState
341 onSpreadShownChanged: {
342 panel.indicators.hide();
343 panel.applicationMenus.hide();
350 minimumTouchPoints: 4
351 maximumTouchPoints: minimumTouchPoints
353 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
354 touchPoints.length >= minimumTouchPoints &&
355 touchPoints.length <= maximumTouchPoints
356 property bool wasPressed: false
358 onRecognisedPressChanged: {
359 if (recognisedPress) {
365 if (status !== TouchGestureArea.Recognized) {
366 if (status === TouchGestureArea.WaitingForTouch) {
367 if (wasPressed && !dragging) {
368 launcher.toggleDrawer(true);
379 objectName: "inputMethod"
382 topMargin: panel.panelHeight
383 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
385 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
390 objectName: "greeterLoader"
392 anchors.topMargin: panel.panelHeight
394 if (shell.mode != "shell") {
395 if (screenWindow.primary) return integratedGreeter;
396 return secondaryGreeter;
398 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
401 item.objectName = "greeter"
403 property bool toggleDrawerAfterUnlock: false
410 // Show drawer in case showHome() requests it
411 if (greeterLoader.toggleDrawerAfterUnlock) {
412 launcher.toggleDrawer(false);
413 greeterLoader.toggleDrawerAfterUnlock = false;
422 id: integratedGreeter
425 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
426 hides: [launcher, panel.indicators, panel.applicationMenus]
427 tabletMode: shell.usageScenario != "phone"
428 forcedUnlock: wizard.active || shell.mode === "full-shell"
429 background: wallpaperResolver.background
430 hasCustomBackground: wallpaperResolver.hasCustomBackground
431 allowFingerprint: !dialogs.hasActiveDialog &&
432 !notifications.topmostIsFullscreen &&
433 !panel.indicators.shown
435 // avoid overlapping with Launcher's edge drag area
436 // FIXME: Fix TouchRegistry & friends and remove this workaround
437 // Issue involves launcher's DDA getting disabled on a long
439 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
442 if (!tutorial.running) {
447 onEmergencyCall: startLockedApp("dialer-app")
454 hides: [launcher, panel.indicators]
459 // See powerConnection for why this is useful
460 id: showGreeterDelayed
463 // Go through the dbus service, because it has checks for whether
464 // we are even allowed to lock or not.
465 DBusUnitySessionService.PromptLock();
474 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
475 // We just received an incoming call while locked. The
476 // indicator will have already launched dialer-app for us, but
477 // there is a race between "hasCalls" changing and the dialer
478 // starting up. So in case we lose that race, we'll start/
479 // focus the dialer ourselves here too. Even if the indicator
480 // didn't launch the dialer for some reason (or maybe a call
481 // started via some other means), if an active call is
482 // happening, we want to be in the dialer.
483 startLockedApp("dialer-app")
493 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
494 !callManager.hasCalls && !wizard.active) {
495 // We don't want to simply call greeter.showNow() here, because
496 // that will take too long. Qt will delay button event
497 // handling until the greeter is done loading and may think the
498 // user held down the power button the whole time, leading to a
499 // power dialog being shown. Instead, delay showing the
500 // greeter until we've finished handling the event. We could
501 // make the greeter load asynchronously instead, but that
502 // introduces a whole host of timing issues, especially with
503 // its animations. So this is simpler.
504 showGreeterDelayed.start();
509 function showHome() {
510 greeter.notifyUserRequestedApp();
512 if (shell.mode === "greeter") {
513 SessionBroadcast.requestHomeShown(AccountsService.user);
515 if (!greeter.active) {
516 launcher.toggleDrawer(false);
518 greeterLoader.toggleDrawerAfterUnlock = true;
532 anchors.fill: parent //because this draws indicator menus
534 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
535 minimizedPanelHeight: units.gu(3)
536 expandedPanelHeight: units.gu(7)
537 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
541 available: tutorial.panelEnabled
542 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
543 && (!greeter || !greeter.hasLockedApp)
544 && !shell.waitingOnGreeter
545 && settings.enableIndicatorMenu
547 model: Indicators.IndicatorsModel {
548 // tablet and phone both use the same profile
549 // FIXME: use just "phone" for greeter too, but first fix
550 // greeter app launching to either load the app inside the
551 // greeter or tell the session to load the app. This will
552 // involve taking the url-dispatcher dbus name and using
553 // SessionBroadcast to tell the session.
554 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
555 Component.onCompleted: load();
561 available: (!greeter || !greeter.shown)
562 && !shell.waitingOnGreeter
563 && !stage.spreadShown
566 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
567 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
569 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
570 || greeter.hasLockedApp
571 greeterShown: greeter && greeter.shown
572 hasKeyboard: shell.hasKeyboard
573 panelState: panelState
574 supportsMultiColorLed: shell.supportsMultiColorLed
579 objectName: "launcher"
581 anchors.top: parent.top
582 anchors.topMargin: inverted ? 0 : panel.panelHeight
583 anchors.bottom: parent.bottom
585 dragAreaWidth: shell.edgeSize
586 available: tutorial.launcherEnabled
587 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
588 && !greeter.hasLockedApp
589 && !shell.waitingOnGreeter
590 inverted: shell.usageScenario !== "desktop"
591 superPressed: physicalKeysMapper.superPressed
592 superTabPressed: physicalKeysMapper.superTabPressed
593 panelWidth: units.gu(settings.launcherWidth)
594 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
595 topPanelHeight: panel.panelHeight
596 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
597 privateMode: greeter.active
598 background: wallpaperResolver.background
600 // It can be assumed that the Launcher and Panel would overlap if
601 // the Panel is open and taking up the full width of the shell
602 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
604 // The "autohideLauncher" setting is only valid in desktop mode
605 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
607 // The Launcher should absolutely not be locked visible under some
609 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
611 onShowDashHome: showHome()
612 onLauncherApplicationSelected: {
613 greeter.notifyUserRequestedApp();
614 shell.activateApplication(appId);
618 panel.indicators.hide();
619 panel.applicationMenus.hide();
622 onDrawerShownChanged: {
624 panel.indicators.hide();
625 panel.applicationMenus.hide();
635 shortcut: Qt.MetaModifier | Qt.Key_A
637 launcher.toggleDrawer(true);
641 shortcut: Qt.AltModifier | Qt.Key_F1
643 launcher.openForKeyboardNavigation();
647 shortcut: Qt.MetaModifier | Qt.Key_0
649 if (LauncherModel.get(9)) {
650 activateApplication(LauncherModel.get(9).appId);
657 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
659 if (LauncherModel.get(index)) {
660 activateApplication(LauncherModel.get(index).appId);
667 KeyboardShortcutsOverlay {
668 objectName: "shortcutsOverlay"
669 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
670 && height < parent.height - padding - panel.panelHeight
671 anchors.centerIn: parent
672 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
673 anchors.verticalCenterOffset: panel.panelHeight/2
675 opacity: enabled ? 0.95 : 0
677 Behavior on opacity {
678 UbuntuNumberAnimation {}
684 objectName: "tutorial"
687 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
688 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
689 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
690 inputMethod.visible ||
691 (launcher.shown && !launcher.lockedVisible) ||
692 panel.indicators.shown || stage.rightEdgeDragProgress > 0
693 usageScenario: shell.usageScenario
694 lastInputTimestamp: inputFilter.lastInputTimestamp
704 deferred: shell.mode === "greeter"
706 function unlockWhenDoneWithWizard() {
708 Connectivity.unlockAllModems();
712 Component.onCompleted: unlockWhenDoneWithWizard()
713 onActiveChanged: unlockWhenDoneWithWizard()
716 MouseArea { // modal notifications prevent interacting with other contents
718 visible: notifications.useModal
725 model: NotificationBackend.Model
727 hasMouse: shell.hasMouse
728 background: wallpaperResolver.background
730 y: topmostIsFullscreen ? 0 : panel.panelHeight
731 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
736 when: overlay.width <= units.gu(60)
738 target: notifications
739 anchors.left: parent.left
740 anchors.right: parent.right
745 when: overlay.width > units.gu(60)
747 target: notifications
748 anchors.left: undefined
749 anchors.right: parent.right
751 PropertyChanges { target: notifications; width: units.gu(38) }
758 enabled: !greeter.shown
760 // NB: it does its own positioning according to the specified edge
764 panel.indicators.hide()
767 material: Component {
773 anchors.centerIn: parent
775 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
776 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
786 objectName: "dialogs"
788 visible: hasActiveDialog
790 usageScenario: shell.usageScenario
791 hasKeyboard: shell.hasKeyboard
793 shutdownFadeOutRectangle.enabled = true;
794 shutdownFadeOutRectangle.visible = true;
795 shutdownFadeOut.start();
800 target: SessionBroadcast
801 onShowHome: if (shell.mode !== "greeter") showHome()
806 objectName: "urlDispatcher"
807 active: shell.mode === "greeter"
808 onUrlRequested: shell.activateURL(url)
815 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
818 ignoreUnknownSignals: true
819 onItemSnapshotRequested: itemGrabber.capture(item)
824 id: cursorHidingTimer
826 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
827 onTriggered: cursor.opacity = 0;
835 topBoundaryOffset: panel.panelHeight
836 enabled: shell.hasMouse && screenWindow.active
839 property bool mouseNeverMoved: true
841 target: cursor; property: "x"; value: shell.width / 2
842 when: cursor.mouseNeverMoved && cursor.visible
845 target: cursor; property: "y"; value: shell.height / 2
846 when: cursor.mouseNeverMoved && cursor.visible
849 confiningItem: stage.itemConfiningMouseCursor
853 readonly property var previewRectangle: stage.previewRectangle.target &&
854 stage.previewRectangle.target.dragging ?
855 stage.previewRectangle : null
857 onPushedLeftBoundary: {
858 if (buttons === Qt.NoButton) {
859 launcher.pushEdge(amount);
860 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
861 previewRectangle.maximizeLeft(amount);
865 onPushedRightBoundary: {
866 if (buttons === Qt.NoButton) {
867 rightEdgeBarrier.push(amount);
868 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
869 previewRectangle.maximizeRight(amount);
873 onPushedTopBoundary: {
874 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
875 previewRectangle.maximize(amount);
878 onPushedTopLeftCorner: {
879 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
880 previewRectangle.maximizeTopLeft(amount);
883 onPushedTopRightCorner: {
884 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
885 previewRectangle.maximizeTopRight(amount);
888 onPushedBottomLeftCorner: {
889 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
890 previewRectangle.maximizeBottomLeft(amount);
893 onPushedBottomRightCorner: {
894 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
895 previewRectangle.maximizeBottomRight(amount);
899 if (previewRectangle) {
900 previewRectangle.stop();
905 mouseNeverMoved = false;
909 Behavior on opacity { UbuntuNumberAnimation {} }
912 // non-visual objects
914 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
919 id: shutdownFadeOutRectangle
926 NumberAnimation on opacity {
931 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
932 DBusUnitySessionService.shutdown();