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/>.
19import QtQuick.Window 2.2
20import AccountsService 0.1
21import Unity.Application 0.1
22import Ubuntu.Components 1.3
23import Ubuntu.Components.Popups 1.3
24import Ubuntu.Gestures 0.1
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Connectivity 0.1
27import Unity.Launcher 0.1
28import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
32import SessionBroadcast 0.1
41import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Session 0.1
43import Unity.Indicators 0.1 as Indicators
45import 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 alias oskEnabled: inputMethod.enabled
63 function updateFocusedAppOrientation() {
64 stage.updateFocusedAppOrientation();
66 function updateFocusedAppOrientationAnimated() {
67 stage.updateFocusedAppOrientationAnimated();
69 property bool hasMouse: false
70 property bool hasKeyboard: false
71 property bool hasTouchscreen: false
72 property bool supportsMultiColorLed: true
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 ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
97 readonly property var mainApp: stage.mainApp
100 _onMainAppChanged((mainApp ? mainApp.appId : ""));
103 target: ApplicationManager
105 if (shell.mainApp && shell.mainApp.appId === appId) {
106 _onMainAppChanged(appId);
111 // Calls attention back to the most important thing that's been focused
112 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
113 // goes over everything if it is locked)
114 // Must be called whenever app focus changes occur, even if the focus change
115 // is "nothing is focused". In that case, call with appId = ""
116 function _onMainAppChanged(appId) {
120 // If this happens on first boot, we may be in the
121 // wizard while receiving a call. 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. The
128 // Greeter will show it when it's notified of the focus.
129 // This can happen if user backs out of dialer back to greeter, then
130 // launches dialer again.
131 greeter.lockedApp = appId;
134 panel.indicators.hide();
135 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
138 // *Always* make sure the greeter knows that the focused app changed
139 if (greeter) greeter.notifyAppFocusRequested(appId);
142 // For autopilot consumption
143 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
145 // Note when greeter is waiting on PAM, so that we can disable edges until
146 // we know which user data to show and whether the session is locked.
147 readonly property bool waitingOnGreeter: greeter && greeter.waiting
149 // True when the user is logged in with no apps running
150 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
152 onAtDesktopChanged: {
153 if (atDesktop && stage) {
158 property real edgeSize: units.gu(settings.edgeDragWidth)
161 id: wallpaperResolver
162 objectName: "wallpaperResolver"
164 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
165 readonly property bool hasCustomBackground: background != defaultBackground
168 id: backgroundSettings
169 schema.id: "org.gnome.desktop.background"
173 AccountsService.backgroundFile,
174 backgroundSettings.pictureUri,
179 readonly property alias greeter: greeterLoader.item
181 function activateApplication(appId) {
182 topLevelSurfaceList.pendingActivation();
184 // Either open the app in our own session, or -- if we're acting as a
185 // greeter -- ask the user's session to open it for us.
186 if (shell.mode === "greeter") {
187 activateURL("application:///" + appId + ".desktop");
194 function activateURL(url) {
195 SessionBroadcast.requestUrlStart(AccountsService.user, url);
196 greeter.notifyUserRequestedApp();
197 panel.indicators.hide();
200 function startApp(appId) {
201 if (ApplicationManager.findApplication(appId)) {
202 ApplicationManager.requestFocusApplication(appId);
204 ApplicationManager.startApplication(appId);
208 function startLockedApp(app) {
209 topLevelSurfaceList.pendingActivation();
211 if (greeter.locked) {
212 greeter.lockedApp = app;
214 startApp(app); // locked apps are always in our same session
218 target: LauncherModel
219 property: "applicationManager"
220 value: ApplicationManager
223 Component.onCompleted: {
224 finishStartUpTimer.start();
232 id: physicalKeysMapper
233 objectName: "physicalKeysMapper"
235 onPowerKeyLongPressed: dialogs.showPowerDialog();
236 onVolumeDownTriggered: volumeControl.volumeDown();
237 onVolumeUpTriggered: volumeControl.volumeUp();
238 onScreenshotTriggered: itemGrabber.capture(shell);
242 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
247 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
248 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
252 objectName: "windowInputMonitor"
253 onHomeKeyActivated: {
254 // Ignore when greeter is active, to avoid pocket presses
255 if (!greeter.active) {
256 launcher.toggleDrawer(/* focusInputField */ false,
257 /* onlyOpen */ false,
258 /* alsoToggleLauncher */ true);
261 onTouchBegun: { cursor.opacity = 0; }
263 // move the (hidden) cursor to the last known touch position
264 var mappedCoords = mapFromItem(null, pos.x, pos.y);
265 cursor.x = mappedCoords.x;
266 cursor.y = mappedCoords.y;
267 cursor.mouseNeverMoved = false;
271 AvailableDesktopArea {
272 id: availableDesktopAreaItem
274 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
275 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
280 schema.id: "com.canonical.Unity8"
287 height: parent.height
291 objectName: "surfaceManager"
293 TopLevelWindowModel {
294 id: topLevelSurfaceList
295 objectName: "topLevelSurfaceList"
296 applicationManager: ApplicationManager // it's a singleton
297 surfaceManager: surfaceMan
306 dragAreaWidth: shell.edgeSize
307 background: wallpaperResolver.background
309 applicationManager: ApplicationManager
310 topLevelSurfaceList: topLevelSurfaceList
311 inputMethodRect: inputMethod.visibleRect
312 rightEdgePushProgress: rightEdgeBarrier.progress
313 availableDesktopArea: availableDesktopAreaItem
315 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
317 : shell.usageScenario
319 mode: usageScenario == "phone" ? "staged"
320 : usageScenario == "tablet" ? "stagedWithSideStage"
323 shellOrientation: shell.orientation
324 shellOrientationAngle: shell.orientationAngle
325 orientations: shell.orientations
326 nativeWidth: shell.nativeWidth
327 nativeHeight: shell.nativeHeight
329 allowInteractivity: (!greeter || !greeter.shown)
330 && panel.indicators.fullyClosed
331 && !notifications.useModal
332 && !launcher.takesFocus
334 suspended: greeter.shown
335 altTabPressed: physicalKeysMapper.altTabPressed
336 oskEnabled: shell.oskEnabled
337 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
339 onSpreadShownChanged: {
340 panel.indicators.hide();
341 panel.applicationMenus.hide();
348 minimumTouchPoints: 4
349 maximumTouchPoints: minimumTouchPoints
351 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
352 touchPoints.length >= minimumTouchPoints &&
353 touchPoints.length <= maximumTouchPoints
354 property bool wasPressed: false
356 onRecognisedPressChanged: {
357 if (recognisedPress) {
363 if (status !== TouchGestureArea.Recognized) {
364 if (status === TouchGestureArea.WaitingForTouch) {
365 if (wasPressed && !dragging) {
366 launcher.toggleDrawer(true);
377 objectName: "inputMethod"
380 topMargin: panel.panelHeight
381 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
383 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
388 objectName: "greeterLoader"
390 anchors.topMargin: panel.panelHeight
391 sourceComponent: shell.mode != "shell" ? integratedGreeter :
392 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
394 item.objectName = "greeter"
396 property bool toggleDrawerAfterUnlock: false
403 // Show drawer in case showHome() requests it
404 if (greeterLoader.toggleDrawerAfterUnlock) {
405 launcher.toggleDrawer(false);
406 greeterLoader.toggleDrawerAfterUnlock = false;
415 id: integratedGreeter
418 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
419 hides: [launcher, panel.indicators, panel.applicationMenus]
420 tabletMode: shell.usageScenario != "phone"
421 forcedUnlock: wizard.active || shell.mode === "full-shell"
422 background: wallpaperResolver.background
423 hasCustomBackground: wallpaperResolver.hasCustomBackground
424 allowFingerprint: !dialogs.hasActiveDialog &&
425 !notifications.topmostIsFullscreen &&
426 !panel.indicators.shown
428 // avoid overlapping with Launcher's edge drag area
429 // FIXME: Fix TouchRegistry & friends and remove this workaround
430 // Issue involves launcher's DDA getting disabled on a long
432 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
435 if (!tutorial.running) {
440 onEmergencyCall: startLockedApp("dialer-app")
445 // See powerConnection for why this is useful
446 id: showGreeterDelayed
449 // Go through the dbus service, because it has checks for whether
450 // we are even allowed to lock or not.
451 DBusUnitySessionService.PromptLock();
460 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
461 // We just received an incoming call while locked. The
462 // indicator will have already launched dialer-app for us, but
463 // there is a race between "hasCalls" changing and the dialer
464 // starting up. So in case we lose that race, we'll start/
465 // focus the dialer ourselves here too. Even if the indicator
466 // didn't launch the dialer for some reason (or maybe a call
467 // started via some other means), if an active call is
468 // happening, we want to be in the dialer.
469 startLockedApp("dialer-app")
479 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
480 !callManager.hasCalls && !wizard.active) {
481 // We don't want to simply call greeter.showNow() here, because
482 // that will take too long. Qt will delay button event
483 // handling until the greeter is done loading and may think the
484 // user held down the power button the whole time, leading to a
485 // power dialog being shown. Instead, delay showing the
486 // greeter until we've finished handling the event. We could
487 // make the greeter load asynchronously instead, but that
488 // introduces a whole host of timing issues, especially with
489 // its animations. So this is simpler.
490 showGreeterDelayed.start();
495 function showHome() {
496 greeter.notifyUserRequestedApp();
498 if (shell.mode === "greeter") {
499 SessionBroadcast.requestHomeShown(AccountsService.user);
501 if (!greeter.active) {
502 launcher.toggleDrawer(false);
504 greeterLoader.toggleDrawerAfterUnlock = true;
518 anchors.fill: parent //because this draws indicator menus
520 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
521 minimizedPanelHeight: units.gu(3)
522 expandedPanelHeight: units.gu(7)
523 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
527 available: tutorial.panelEnabled
528 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
529 && (!greeter || !greeter.hasLockedApp)
530 && !shell.waitingOnGreeter
531 && settings.enableIndicatorMenu
533 model: Indicators.IndicatorsModel {
534 // tablet and phone both use the same profile
535 // FIXME: use just "phone" for greeter too, but first fix
536 // greeter app launching to either load the app inside the
537 // greeter or tell the session to load the app. This will
538 // involve taking the url-dispatcher dbus name and using
539 // SessionBroadcast to tell the session.
540 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
541 Component.onCompleted: load();
547 available: (!greeter || !greeter.shown)
548 && !shell.waitingOnGreeter
549 && !stage.spreadShown
552 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
553 ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
555 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
556 || greeter.hasLockedApp
557 greeterShown: greeter && greeter.shown
558 hasKeyboard: shell.hasKeyboard
559 supportsMultiColorLed: shell.supportsMultiColorLed
564 objectName: "launcher"
566 anchors.top: parent.top
567 anchors.topMargin: inverted ? 0 : panel.panelHeight
568 anchors.bottom: parent.bottom
570 dragAreaWidth: shell.edgeSize
571 available: tutorial.launcherEnabled
572 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
573 && !greeter.hasLockedApp
574 && !shell.waitingOnGreeter
575 inverted: shell.usageScenario !== "desktop"
576 superPressed: physicalKeysMapper.superPressed
577 superTabPressed: physicalKeysMapper.superTabPressed
578 panelWidth: units.gu(settings.launcherWidth)
579 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
580 topPanelHeight: panel.panelHeight
581 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
582 privateMode: greeter.active
583 background: wallpaperResolver.background
585 // It can be assumed that the Launcher and Panel would overlap if
586 // the Panel is open and taking up the full width of the shell
587 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
589 // The "autohideLauncher" setting is only valid in desktop mode
590 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
592 // The Launcher should absolutely not be locked visible under some
594 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
596 onShowDashHome: showHome()
597 onLauncherApplicationSelected: {
598 greeter.notifyUserRequestedApp();
599 shell.activateApplication(appId);
603 panel.indicators.hide();
604 panel.applicationMenus.hide();
607 onDrawerShownChanged: {
609 panel.indicators.hide();
610 panel.applicationMenus.hide();
620 shortcut: Qt.MetaModifier | Qt.Key_A
622 launcher.toggleDrawer(true);
626 shortcut: Qt.AltModifier | Qt.Key_F1
628 launcher.openForKeyboardNavigation();
632 shortcut: Qt.MetaModifier | Qt.Key_0
634 if (LauncherModel.get(9)) {
635 activateApplication(LauncherModel.get(9).appId);
642 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
644 if (LauncherModel.get(index)) {
645 activateApplication(LauncherModel.get(index).appId);
652 KeyboardShortcutsOverlay {
653 objectName: "shortcutsOverlay"
654 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
655 && height < parent.height - padding - panel.panelHeight
656 anchors.centerIn: parent
657 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
658 anchors.verticalCenterOffset: panel.panelHeight/2
660 opacity: enabled ? 0.95 : 0
662 Behavior on opacity {
663 UbuntuNumberAnimation {}
669 objectName: "tutorial"
672 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
673 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
674 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
675 inputMethod.visible ||
676 (launcher.shown && !launcher.lockedVisible) ||
677 panel.indicators.shown || stage.rightEdgeDragProgress > 0
678 usageScenario: shell.usageScenario
679 lastInputTimestamp: inputFilter.lastInputTimestamp
689 deferred: shell.mode === "greeter"
691 function unlockWhenDoneWithWizard() {
693 Connectivity.unlockAllModems();
697 Component.onCompleted: unlockWhenDoneWithWizard()
698 onActiveChanged: unlockWhenDoneWithWizard()
701 MouseArea { // modal notifications prevent interacting with other contents
703 visible: notifications.useModal
710 model: NotificationBackend.Model
712 hasMouse: shell.hasMouse
713 background: wallpaperResolver.background
715 y: topmostIsFullscreen ? 0 : panel.panelHeight
716 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
721 when: overlay.width <= units.gu(60)
723 target: notifications
724 anchors.left: parent.left
725 anchors.right: parent.right
730 when: overlay.width > units.gu(60)
732 target: notifications
733 anchors.left: undefined
734 anchors.right: parent.right
736 PropertyChanges { target: notifications; width: units.gu(38) }
743 enabled: !greeter.shown
745 // NB: it does its own positioning according to the specified edge
749 panel.indicators.hide()
752 material: Component {
758 anchors.centerIn: parent
760 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
761 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
771 objectName: "dialogs"
773 visible: hasActiveDialog
775 usageScenario: shell.usageScenario
776 hasKeyboard: shell.hasKeyboard
778 shutdownFadeOutRectangle.enabled = true;
779 shutdownFadeOutRectangle.visible = true;
780 shutdownFadeOut.start();
785 target: SessionBroadcast
786 onShowHome: if (shell.mode !== "greeter") showHome()
791 objectName: "urlDispatcher"
792 active: shell.mode === "greeter"
793 onUrlRequested: shell.activateURL(url)
800 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
803 ignoreUnknownSignals: true
804 onItemSnapshotRequested: itemGrabber.capture(item)
809 id: cursorHidingTimer
811 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
812 onTriggered: cursor.opacity = 0;
818 visible: shell.hasMouse
820 topBoundaryOffset: panel.panelHeight
822 confiningItem: stage.itemConfiningMouseCursor
824 property bool mouseNeverMoved: true
826 target: cursor; property: "x"; value: shell.width / 2
827 when: cursor.mouseNeverMoved && cursor.visible
830 target: cursor; property: "y"; value: shell.height / 2
831 when: cursor.mouseNeverMoved && cursor.visible
836 readonly property var previewRectangle: stage.previewRectangle.target &&
837 stage.previewRectangle.target.dragging ?
838 stage.previewRectangle : null
840 onPushedLeftBoundary: {
841 if (buttons === Qt.NoButton) {
842 launcher.pushEdge(amount);
843 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
844 previewRectangle.maximizeLeft(amount);
848 onPushedRightBoundary: {
849 if (buttons === Qt.NoButton) {
850 rightEdgeBarrier.push(amount);
851 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
852 previewRectangle.maximizeRight(amount);
856 onPushedTopBoundary: {
857 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
858 previewRectangle.maximize(amount);
861 onPushedTopLeftCorner: {
862 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
863 previewRectangle.maximizeTopLeft(amount);
866 onPushedTopRightCorner: {
867 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
868 previewRectangle.maximizeTopRight(amount);
871 onPushedBottomLeftCorner: {
872 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
873 previewRectangle.maximizeBottomLeft(amount);
876 onPushedBottomRightCorner: {
877 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
878 previewRectangle.maximizeBottomRight(amount);
882 if (previewRectangle) {
883 previewRectangle.stop();
888 mouseNeverMoved = false;
892 Behavior on opacity { UbuntuNumberAnimation {} }
895 // non-visual objects
897 focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
902 id: shutdownFadeOutRectangle
909 NumberAnimation on opacity {
914 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
915 DBusUnitySessionService.shutdown();
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list