2 * Copyright (C) 2018 The UBports project
3 * Copyright (C) 2013-2016 Canonical, Ltd.
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 QtGraphicalEffects 1.0
21import AccountsService 0.1
22import Unity.Application 0.1
23import Ubuntu.Components 1.3
24import Ubuntu.Components.Popups 1.3
25import Ubuntu.Gestures 0.1
26import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
29import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
34import SessionBroadcast 0.1
43import Unity.Notifications 1.0 as NotificationBackend
44import Unity.Session 0.1
45import Unity.DashCommunicator 0.1
46import Unity.Indicators 0.1 as Indicators
48import WindowManager 0.1
54 theme.name: "Ubuntu.Components.Themes.SuruDark"
56 // to be set from outside
57 property int orientationAngle: 0
58 property int orientation
59 property Orientations orientations
60 property real nativeWidth
61 property real nativeHeight
62 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
63 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
64 property string mode: "full-greeter"
65 property alias oskEnabled: inputMethod.enabled
66 function updateFocusedAppOrientation() {
67 stage.updateFocusedAppOrientation();
69 function updateFocusedAppOrientationAnimated() {
70 stage.updateFocusedAppOrientationAnimated();
72 property bool hasMouse: 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 != "" && appId != "unity8-dash") {
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();
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) {
248 onTouchBegun: { cursor.opacity = 0; }
250 // move the (hidden) cursor to the last known touch position
251 var mappedCoords = mapFromItem(null, pos.x, pos.y);
252 cursor.x = mappedCoords.x;
253 cursor.y = mappedCoords.y;
254 cursor.mouseNeverMoved = false;
260 schema.id: "com.canonical.Unity8"
267 height: parent.height
269 TopLevelSurfaceList {
270 id: topLevelSurfaceList
271 objectName: "topLevelSurfaceList"
272 applicationsModel: ApplicationManager
281 dragAreaWidth: shell.edgeSize
282 background: wallpaperResolver.background
283 leftEdgeDragProgress: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 :
284 Math.max(0, (launcher.dragDistance * (stage.width - launcher.panelWidth) / stage.width) - launcher.panelWidth)
286 applicationManager: ApplicationManager
287 topLevelSurfaceList: topLevelSurfaceList
288 inputMethodRect: inputMethod.visibleRect
290 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
292 : shell.usageScenario
294 mode: usageScenario == "phone" ? "staged"
295 : usageScenario == "tablet" ? "stagedWithSideStage"
298 shellOrientation: shell.orientation
299 shellOrientationAngle: shell.orientationAngle
300 orientations: shell.orientations
301 nativeWidth: shell.nativeWidth
302 nativeHeight: shell.nativeHeight
304 interactive: (!greeter || !greeter.shown)
305 && panel.indicators.fullyClosed
306 && launcher.progress == 0
307 && !notifications.useModal
309 onInteractiveChanged: { if (interactive) { focus = true; } }
311 leftMargin: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
312 suspended: greeter.shown
313 keepDashRunning: launcher.shown || launcher.dashSwipe
314 altTabPressed: physicalKeysMapper.altTabPressed
315 oskEnabled: shell.oskEnabled
316 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
322 objectName: "inputMethod"
325 topMargin: panel.panelHeight
326 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
328 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
333 objectName: "greeterLoader"
335 anchors.topMargin: panel.panelHeight
336 sourceComponent: shell.mode != "shell" ? integratedGreeter :
337 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
339 item.objectName = "greeter"
344 id: integratedGreeter
347 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
348 hides: [launcher, panel.indicators]
349 tabletMode: shell.usageScenario != "phone"
350 launcherOffset: launcher.progress
351 forcedUnlock: wizard.active && !wizard.isUpdate || shell.mode === "full-shell"
352 background: wallpaperResolver.cachedBackground
353 hasCustomBackground: wallpaperResolver.hasCustomBackground
354 allowFingerprint: !dialogs.hasActiveDialog &&
355 !notifications.topmostIsFullscreen &&
356 !panel.indicators.shown
358 // avoid overlapping with Launcher's edge drag area
359 // FIXME: Fix TouchRegistry & friends and remove this workaround
360 // Issue involves launcher's DDA getting disabled on a long
362 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
369 if (!tutorial.running) {
374 onEmergencyCall: startLockedApp("dialer-app")
379 // See powerConnection for why this is useful
380 id: showGreeterDelayed
392 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
393 // We just received an incoming call while locked. The
394 // indicator will have already launched dialer-app for us, but
395 // there is a race between "hasCalls" changing and the dialer
396 // starting up. So in case we lose that race, we'll start/
397 // focus the dialer ourselves here too. Even if the indicator
398 // didn't launch the dialer for some reason (or maybe a call
399 // started via some other means), if an active call is
400 // happening, we want to be in the dialer.
401 startLockedApp("dialer-app")
411 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
412 !callManager.hasCalls && !wizard.active) {
413 // We don't want to simply call greeter.showNow() here, because
414 // that will take too long. Qt will delay button event
415 // handling until the greeter is done loading and may think the
416 // user held down the power button the whole time, leading to a
417 // power dialog being shown. Instead, delay showing the
418 // greeter until we've finished handling the event. We could
419 // make the greeter load asynchronously instead, but that
420 // introduces a whole host of timing issues, especially with
421 // its animations. So this is simpler.
422 showGreeterDelayed.start();
427 function showHome() {
428 greeter.notifyUserRequestedApp();
430 if (shell.mode === "greeter") {
431 SessionBroadcast.requestHomeShown(AccountsService.user);
433 var animate = !LightDMService.greeter.active && !stages.shown;
434 dash.setCurrentScope(0, animate, false);
435 ApplicationManager.requestFocusApplication("unity8-dash");
439 function showDash() {
440 if (greeter.notifyShowingDashFromDrag()) {
444 if (!greeter.locked && tutorial.launcherLongSwipeEnabled
445 && (ApplicationManager.focusedApplicationId != "unity8-dash" || stage.spreadShown)) {
446 ApplicationManager.requestFocusApplication("unity8-dash")
452 // Red filter overlay
455 opacity: settings.redFilterOpacity
456 visible: settings.redFilterEnabled && settings.redFilterOpacity > 0.01
471 anchors.fill: parent //because this draws indicator menus
474 available: tutorial.panelEnabled
475 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
476 && (!greeter || !greeter.hasLockedApp)
477 && !shell.waitingOnGreeter
478 && settings.enableIndicatorMenu
479 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
481 minimizedPanelHeight: units.gu(3)
482 expandedPanelHeight: units.gu(7)
484 indicatorsModel: Indicators.IndicatorsModel {
485 // tablet and phone both use the same profile
486 // FIXME: use just "phone" for greeter too, but first fix
487 // greeter app launching to either load the app inside the
488 // greeter or tell the session to load the app. This will
489 // involve taking the url-dispatcher dbus name and using
490 // SessionBroadcast to tell the session.
491 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
492 Component.onCompleted: load();
497 greeterShown: greeter.shown
500 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
501 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
503 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
504 || greeter.hasLockedApp
505 locked: greeter && greeter.active
510 objectName: "launcher"
513 * Since the Dash doesn't have the same controll over surfaces that the
514 * Shell does, it can't slowly move the scope out of the way, as the shell
515 * does with apps, and the dash is show instantly. This allows for some
516 * leeway and prevents accidental home swipes.
518 readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
519 readonly property bool dashSwipe: progress > offset
521 anchors.top: parent.top
522 anchors.topMargin: inverted ? 0 : panel.panelHeight
523 anchors.bottom: parent.bottom
525 dragAreaWidth: shell.edgeSize
526 available: tutorial.launcherEnabled
527 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
528 && !greeter.hasLockedApp
529 && !shell.waitingOnGreeter
530 && settings.enableLauncher
531 inverted: shell.usageScenario !== "desktop"
532 superPressed: physicalKeysMapper.superPressed
533 superTabPressed: physicalKeysMapper.superTabPressed
534 panelWidth: units.gu(settings.launcherWidth)
535 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
537 onShowDashHome: showHome()
539 onDashSwipeChanged: {
541 dash.setCurrentScope(0, false, true)
544 onLauncherApplicationSelected: {
545 greeter.notifyUserRequestedApp();
546 shell.activateApplication(appId);
550 panel.indicators.hide()
560 shortcut: Qt.AltModifier | Qt.Key_F1
562 launcher.openForKeyboardNavigation();
566 shortcut: Qt.MetaModifier | Qt.Key_0
568 if (LauncherModel.get(9)) {
569 activateApplication(LauncherModel.get(9).appId);
576 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
578 if (LauncherModel.get(index)) {
579 activateApplication(LauncherModel.get(index).appId);
586 KeyboardShortcutsOverlay {
587 objectName: "shortcutsOverlay"
588 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
589 && height < parent.height - padding - panel.panelHeight
590 anchors.centerIn: parent
591 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
592 anchors.verticalCenterOffset: panel.panelHeight/2
594 opacity: enabled ? 0.95 : 0
596 Behavior on opacity {
597 UbuntuNumberAnimation {}
603 objectName: "tutorial"
606 paused: callManager.hasCalls || !greeter || greeter.active ||
608 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
609 inputMethod.visible ||
610 (launcher.shown && !launcher.lockedVisible) ||
611 panel.indicators.shown || stage.rightEdgeDragProgress > 0
612 usageScenario: shell.usageScenario
613 lastInputTimestamp: inputFilter.lastInputTimestamp
623 deferred: shell.mode === "greeter"
625 function unlockWhenDoneWithWizard() {
627 Connectivity.unlockAllModems();
631 Component.onCompleted: unlockWhenDoneWithWizard()
632 onActiveChanged: unlockWhenDoneWithWizard()
635 MouseArea { // modal notifications prevent interacting with other contents
637 visible: notifications.useModal
644 model: NotificationBackend.Model
646 hasMouse: shell.hasMouse
647 background: wallpaperResolver.cachedBackground
649 y: topmostIsFullscreen ? 0 : panel.panelHeight
650 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
655 when: overlay.width <= units.gu(60)
657 target: notifications
658 anchors.left: parent.left
659 anchors.right: parent.right
664 when: overlay.width > units.gu(60)
666 target: notifications
667 anchors.left: undefined
668 anchors.right: parent.right
670 PropertyChanges { target: notifications; width: units.gu(38) }
678 objectName: "dialogs"
681 usageScenario: shell.usageScenario
683 shutdownFadeOutRectangle.enabled = true;
684 shutdownFadeOutRectangle.visible = true;
685 shutdownFadeOut.start();
690 target: SessionBroadcast
691 onShowHome: if (shell.mode !== "greeter") showHome()
696 objectName: "urlDispatcher"
697 active: shell.mode === "greeter"
698 onUrlRequested: shell.activateURL(url)
705 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
708 ignoreUnknownSignals: true
709 onItemSnapshotRequested: itemGrabber.capture(item)
714 id: cursorHidingTimer
716 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
717 onTriggered: cursor.opacity = 0;
723 visible: shell.hasMouse
725 topBoundaryOffset: panel.panelHeight
727 confiningItem: stage.itemConfiningMouseCursor
729 property bool mouseNeverMoved: true
731 target: cursor; property: "x"; value: shell.width / 2
732 when: cursor.mouseNeverMoved && cursor.visible
735 target: cursor; property: "y"; value: shell.height / 2
736 when: cursor.mouseNeverMoved && cursor.visible
741 readonly property var previewRectangle: stage.previewRectangle.target &&
742 stage.previewRectangle.target.dragging ?
743 stage.previewRectangle : null
745 onPushedLeftBoundary: {
746 if (buttons === Qt.NoButton) {
747 launcher.pushEdge(amount);
748 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
749 previewRectangle.maximizeLeft(amount);
753 onPushedRightBoundary: {
754 if (buttons === Qt.NoButton) {
755 stage.pushRightEdge(amount);
756 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
757 previewRectangle.maximizeRight(amount);
761 onPushedTopBoundary: {
762 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
763 previewRectangle.maximize(amount);
766 onPushedTopLeftCorner: {
767 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
768 previewRectangle.maximizeTopLeft(amount);
771 onPushedTopRightCorner: {
772 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
773 previewRectangle.maximizeTopRight(amount);
776 onPushedBottomLeftCorner: {
777 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
778 previewRectangle.maximizeBottomLeft(amount);
781 onPushedBottomRightCorner: {
782 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
783 previewRectangle.maximizeBottomRight(amount);
787 if (previewRectangle) {
788 previewRectangle.stop();
793 mouseNeverMoved = false;
797 Behavior on opacity { UbuntuNumberAnimation {} }
800 // non-visual objects
805 id: shutdownFadeOutRectangle
812 NumberAnimation on opacity {
817 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
818 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