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.map(stage.supportedOrientations);
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
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))
340 onSpreadShownChanged: {
341 panel.indicators.hide();
342 panel.applicationMenus.hide();
349 minimumTouchPoints: 4
350 maximumTouchPoints: minimumTouchPoints
352 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
353 touchPoints.length >= minimumTouchPoints &&
354 touchPoints.length <= maximumTouchPoints
355 property bool wasPressed: false
357 onRecognisedPressChanged: {
358 if (recognisedPress) {
364 if (status !== TouchGestureArea.Recognized) {
365 if (status === TouchGestureArea.WaitingForTouch) {
366 if (wasPressed && !dragging) {
367 launcher.toggleDrawer(true);
378 objectName: "inputMethod"
381 topMargin: panel.panelHeight
382 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
384 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
389 objectName: "greeterLoader"
391 anchors.topMargin: panel.panelHeight
392 sourceComponent: shell.mode != "shell" ? integratedGreeter :
393 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
395 item.objectName = "greeter"
397 property bool toggleDrawerAfterUnlock: false
404 // Show drawer in case showHome() requests it
405 if (greeterLoader.toggleDrawerAfterUnlock) {
406 launcher.toggleDrawer(false);
407 greeterLoader.toggleDrawerAfterUnlock = false;
416 id: integratedGreeter
419 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
420 hides: [launcher, panel.indicators, panel.applicationMenus]
421 tabletMode: shell.usageScenario != "phone"
422 forcedUnlock: wizard.active || shell.mode === "full-shell"
423 background: wallpaperResolver.background
424 hasCustomBackground: wallpaperResolver.hasCustomBackground
425 allowFingerprint: !dialogs.hasActiveDialog &&
426 !notifications.topmostIsFullscreen &&
427 !panel.indicators.shown
429 // avoid overlapping with Launcher's edge drag area
430 // FIXME: Fix TouchRegistry & friends and remove this workaround
431 // Issue involves launcher's DDA getting disabled on a long
433 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
436 if (!tutorial.running) {
441 onEmergencyCall: startLockedApp("dialer-app")
446 // See powerConnection for why this is useful
447 id: showGreeterDelayed
450 // Go through the dbus service, because it has checks for whether
451 // we are even allowed to lock or not.
452 DBusUnitySessionService.PromptLock();
461 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
462 // We just received an incoming call while locked. The
463 // indicator will have already launched dialer-app for us, but
464 // there is a race between "hasCalls" changing and the dialer
465 // starting up. So in case we lose that race, we'll start/
466 // focus the dialer ourselves here too. Even if the indicator
467 // didn't launch the dialer for some reason (or maybe a call
468 // started via some other means), if an active call is
469 // happening, we want to be in the dialer.
470 startLockedApp("dialer-app")
480 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
481 !callManager.hasCalls && !wizard.active) {
482 // We don't want to simply call greeter.showNow() here, because
483 // that will take too long. Qt will delay button event
484 // handling until the greeter is done loading and may think the
485 // user held down the power button the whole time, leading to a
486 // power dialog being shown. Instead, delay showing the
487 // greeter until we've finished handling the event. We could
488 // make the greeter load asynchronously instead, but that
489 // introduces a whole host of timing issues, especially with
490 // its animations. So this is simpler.
491 showGreeterDelayed.start();
496 function showHome() {
497 greeter.notifyUserRequestedApp();
499 if (shell.mode === "greeter") {
500 SessionBroadcast.requestHomeShown(AccountsService.user);
502 if (!greeter.active) {
503 launcher.toggleDrawer(false);
505 greeterLoader.toggleDrawerAfterUnlock = true;
519 anchors.fill: parent //because this draws indicator menus
521 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
522 minimizedPanelHeight: units.gu(3)
523 expandedPanelHeight: units.gu(7)
524 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
528 available: tutorial.panelEnabled
529 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
530 && (!greeter || !greeter.hasLockedApp)
531 && !shell.waitingOnGreeter
532 && settings.enableIndicatorMenu
534 model: Indicators.IndicatorsModel {
535 // tablet and phone both use the same profile
536 // FIXME: use just "phone" for greeter too, but first fix
537 // greeter app launching to either load the app inside the
538 // greeter or tell the session to load the app. This will
539 // involve taking the url-dispatcher dbus name and using
540 // SessionBroadcast to tell the session.
541 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
542 Component.onCompleted: load();
548 available: (!greeter || !greeter.shown)
549 && !shell.waitingOnGreeter
550 && !stage.spreadShown
553 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
554 ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
556 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
557 || greeter.hasLockedApp
558 greeterShown: greeter && greeter.shown
559 hasKeyboard: shell.hasKeyboard
560 supportsMultiColorLed: shell.supportsMultiColorLed
565 objectName: "launcher"
567 anchors.top: parent.top
568 anchors.topMargin: inverted ? 0 : panel.panelHeight
569 anchors.bottom: parent.bottom
571 dragAreaWidth: shell.edgeSize
572 available: tutorial.launcherEnabled
573 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
574 && !greeter.hasLockedApp
575 && !shell.waitingOnGreeter
576 inverted: shell.usageScenario !== "desktop"
577 superPressed: physicalKeysMapper.superPressed
578 superTabPressed: physicalKeysMapper.superTabPressed
579 panelWidth: units.gu(settings.launcherWidth)
580 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
581 topPanelHeight: panel.panelHeight
582 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
583 privateMode: greeter.active
584 background: wallpaperResolver.background
586 // It can be assumed that the Launcher and Panel would overlap if
587 // the Panel is open and taking up the full width of the shell
588 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
590 // The "autohideLauncher" setting is only valid in desktop mode
591 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
593 // The Launcher should absolutely not be locked visible under some
595 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
597 onShowDashHome: showHome()
598 onLauncherApplicationSelected: {
599 greeter.notifyUserRequestedApp();
600 shell.activateApplication(appId);
604 panel.indicators.hide();
605 panel.applicationMenus.hide();
608 onDrawerShownChanged: {
610 panel.indicators.hide();
611 panel.applicationMenus.hide();
621 shortcut: Qt.MetaModifier | Qt.Key_A
623 launcher.toggleDrawer(true);
627 shortcut: Qt.AltModifier | Qt.Key_F1
629 launcher.openForKeyboardNavigation();
633 shortcut: Qt.MetaModifier | Qt.Key_0
635 if (LauncherModel.get(9)) {
636 activateApplication(LauncherModel.get(9).appId);
643 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
645 if (LauncherModel.get(index)) {
646 activateApplication(LauncherModel.get(index).appId);
653 KeyboardShortcutsOverlay {
654 objectName: "shortcutsOverlay"
655 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
656 && height < parent.height - padding - panel.panelHeight
657 anchors.centerIn: parent
658 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
659 anchors.verticalCenterOffset: panel.panelHeight/2
661 opacity: enabled ? 0.95 : 0
663 Behavior on opacity {
664 UbuntuNumberAnimation {}
670 objectName: "tutorial"
673 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
674 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
675 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
676 inputMethod.visible ||
677 (launcher.shown && !launcher.lockedVisible) ||
678 panel.indicators.shown || stage.rightEdgeDragProgress > 0
679 usageScenario: shell.usageScenario
680 lastInputTimestamp: inputFilter.lastInputTimestamp
690 deferred: shell.mode === "greeter"
692 function unlockWhenDoneWithWizard() {
694 Connectivity.unlockAllModems();
698 Component.onCompleted: unlockWhenDoneWithWizard()
699 onActiveChanged: unlockWhenDoneWithWizard()
702 MouseArea { // modal notifications prevent interacting with other contents
704 visible: notifications.useModal
711 model: NotificationBackend.Model
713 hasMouse: shell.hasMouse
714 background: wallpaperResolver.background
716 y: topmostIsFullscreen ? 0 : panel.panelHeight
717 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
722 when: overlay.width <= units.gu(60)
724 target: notifications
725 anchors.left: parent.left
726 anchors.right: parent.right
731 when: overlay.width > units.gu(60)
733 target: notifications
734 anchors.left: undefined
735 anchors.right: parent.right
737 PropertyChanges { target: notifications; width: units.gu(38) }
744 enabled: !greeter.shown
746 // NB: it does its own positioning according to the specified edge
750 panel.indicators.hide()
753 material: Component {
759 anchors.centerIn: parent
761 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
762 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
772 objectName: "dialogs"
774 visible: hasActiveDialog
776 usageScenario: shell.usageScenario
777 hasKeyboard: shell.hasKeyboard
779 shutdownFadeOutRectangle.enabled = true;
780 shutdownFadeOutRectangle.visible = true;
781 shutdownFadeOut.start();
786 target: SessionBroadcast
787 onShowHome: if (shell.mode !== "greeter") showHome()
792 objectName: "urlDispatcher"
793 active: shell.mode === "greeter"
794 onUrlRequested: shell.activateURL(url)
801 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
804 ignoreUnknownSignals: true
805 onItemSnapshotRequested: itemGrabber.capture(item)
810 id: cursorHidingTimer
812 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
813 onTriggered: cursor.opacity = 0;
819 visible: shell.hasMouse
821 topBoundaryOffset: panel.panelHeight
823 confiningItem: stage.itemConfiningMouseCursor
825 property bool mouseNeverMoved: true
827 target: cursor; property: "x"; value: shell.width / 2
828 when: cursor.mouseNeverMoved && cursor.visible
831 target: cursor; property: "y"; value: shell.height / 2
832 when: cursor.mouseNeverMoved && cursor.visible
837 readonly property var previewRectangle: stage.previewRectangle.target &&
838 stage.previewRectangle.target.dragging ?
839 stage.previewRectangle : null
841 onPushedLeftBoundary: {
842 if (buttons === Qt.NoButton) {
843 launcher.pushEdge(amount);
844 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
845 previewRectangle.maximizeLeft(amount);
849 onPushedRightBoundary: {
850 if (buttons === Qt.NoButton) {
851 rightEdgeBarrier.push(amount);
852 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
853 previewRectangle.maximizeRight(amount);
857 onPushedTopBoundary: {
858 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
859 previewRectangle.maximize(amount);
862 onPushedTopLeftCorner: {
863 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
864 previewRectangle.maximizeTopLeft(amount);
867 onPushedTopRightCorner: {
868 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
869 previewRectangle.maximizeTopRight(amount);
872 onPushedBottomLeftCorner: {
873 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
874 previewRectangle.maximizeBottomLeft(amount);
877 onPushedBottomRightCorner: {
878 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
879 previewRectangle.maximizeBottomRight(amount);
883 if (previewRectangle) {
884 previewRectangle.stop();
889 mouseNeverMoved = false;
893 Behavior on opacity { UbuntuNumberAnimation {} }
896 // non-visual objects
898 focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
903 id: shutdownFadeOutRectangle
910 NumberAnimation on opacity {
915 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
916 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