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/>.
19import QtQuick.Window 2.2
20import AccountsService 0.1
21import QtMir.Application 0.1
22import Lomiri.Components 1.3
23import Lomiri.Components.Popups 1.3
24import Lomiri.Gestures 0.1
25import Lomiri.Telephony 0.1 as Telephony
26import Lomiri.ModemConnectivity 0.1
27import Lomiri.Launcher 0.1
28import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
32import SessionBroadcast 0.1
41import "Components/PanelState"
42import Lomiri.Notifications 1.0 as NotificationBackend
43import Lomiri.Session 0.1
44import Lomiri.Indicators 0.1 as Indicators
46import WindowManager 1.0
52 theme.name: "Lomiri.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 // 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
117 readonly property var topLevelSurfaceList: {
118 if (!WMScreen.currentWorkspace) return null;
119 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
123 _onMainAppChanged((mainApp ? mainApp.appId : ""));
126 target: ApplicationManager
128 if (shell.mainApp && shell.mainApp.appId === appId) {
129 _onMainAppChanged(appId);
134 // Calls attention back to the most important thing that's been focused
135 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
136 // goes over everything if it is locked)
137 // Must be called whenever app focus changes occur, even if the focus change
138 // is "nothing is focused". In that case, call with appId = ""
139 function _onMainAppChanged(appId) {
143 // If this happens on first boot, we may be in the
144 // wizard while receiving a call. A call is more
145 // important than the wizard so just bail out of it.
149 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
150 // If we are in the middle of a call, make dialer lockedApp. The
151 // Greeter will show it when it's notified of the focus.
152 // This can happen if user backs out of dialer back to greeter, then
153 // launches dialer again.
154 greeter.lockedApp = appId;
157 panel.indicators.hide();
158 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
161 // *Always* make sure the greeter knows that the focused app changed
162 if (greeter) greeter.notifyAppFocusRequested(appId);
165 // For autopilot consumption
166 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
168 // Note when greeter is waiting on PAM, so that we can disable edges until
169 // we know which user data to show and whether the session is locked.
170 readonly property bool waitingOnGreeter: greeter && greeter.waiting
172 // True when the user is logged in with no apps running
173 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
175 onAtDesktopChanged: {
176 if (atDesktop && stage) {
181 property real edgeSize: units.gu(settings.edgeDragWidth)
184 id: wallpaperResolver
185 objectName: "wallpaperResolver"
187 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
188 readonly property bool hasCustomBackground: background != defaultBackground
191 id: backgroundSettings
192 schema.id: "org.gnome.desktop.background"
196 AccountsService.backgroundFile,
197 backgroundSettings.pictureUri,
202 readonly property alias greeter: greeterLoader.item
204 function activateApplication(appId) {
205 topLevelSurfaceList.pendingActivation();
207 // Either open the app in our own session, or -- if we're acting as a
208 // greeter -- ask the user's session to open it for us.
209 if (shell.mode === "greeter") {
210 activateURL("application:///" + appId + ".desktop");
217 function activateURL(url) {
218 SessionBroadcast.requestUrlStart(AccountsService.user, url);
219 greeter.notifyUserRequestedApp();
220 panel.indicators.hide();
223 function startApp(appId) {
224 if (!ApplicationManager.findApplication(appId)) {
225 ApplicationManager.startApplication(appId);
227 ApplicationManager.requestFocusApplication(appId);
230 function startLockedApp(app) {
231 topLevelSurfaceList.pendingActivation();
233 if (greeter.locked) {
234 greeter.lockedApp = app;
236 startApp(app); // locked apps are always in our same session
240 target: LauncherModel
241 property: "applicationManager"
242 value: ApplicationManager
245 Component.onCompleted: {
246 finishStartUpTimer.start();
254 id: physicalKeysMapper
255 objectName: "physicalKeysMapper"
257 onPowerKeyLongPressed: dialogs.showPowerDialog();
258 onVolumeDownTriggered: volumeControl.volumeDown();
259 onVolumeUpTriggered: volumeControl.volumeUp();
260 onScreenshotTriggered: itemGrabber.capture(shell);
264 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
269 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
270 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
274 objectName: "windowInputMonitor"
275 onHomeKeyActivated: {
276 // Ignore when greeter is active, to avoid pocket presses
277 if (!greeter.active) {
278 launcher.toggleDrawer(/* focusInputField */ false,
279 /* onlyOpen */ false,
280 /* alsoToggleLauncher */ true);
283 onTouchBegun: { cursor.opacity = 0; }
285 // move the (hidden) cursor to the last known touch position
286 var mappedCoords = mapFromItem(null, pos.x, pos.y);
287 cursor.x = mappedCoords.x;
288 cursor.y = mappedCoords.y;
289 cursor.mouseNeverMoved = false;
293 AvailableDesktopArea {
294 id: availableDesktopAreaItem
296 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
297 anchors.leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
302 schema.id: "com.lomiri.Shell"
307 objectName: "panelState"
314 height: parent.height
322 dragAreaWidth: shell.edgeSize
323 background: wallpaperResolver.background
324 backgroundSourceSize: shell.largestScreenDimension
326 applicationManager: ApplicationManager
327 topLevelSurfaceList: shell.topLevelSurfaceList
328 inputMethodRect: inputMethod.visibleRect
329 rightEdgePushProgress: rightEdgeBarrier.progress
330 availableDesktopArea: availableDesktopAreaItem
331 launcherLeftMargin: launcher.visibleWidth
333 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
335 : shell.usageScenario
337 mode: usageScenario == "phone" ? "staged"
338 : usageScenario == "tablet" ? "stagedWithSideStage"
341 shellOrientation: shell.orientation
342 shellOrientationAngle: shell.orientationAngle
343 orientations: shell.orientations
344 nativeWidth: shell.nativeWidth
345 nativeHeight: shell.nativeHeight
347 allowInteractivity: (!greeter || !greeter.shown)
348 && panel.indicators.fullyClosed
349 && !notifications.useModal
350 && !launcher.takesFocus
352 suspended: greeter.shown
353 altTabPressed: physicalKeysMapper.altTabPressed
354 oskEnabled: shell.oskEnabled
355 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
356 panelState: panelState
358 onSpreadShownChanged: {
359 panel.indicators.hide();
360 panel.applicationMenus.hide();
367 minimumTouchPoints: 4
368 maximumTouchPoints: minimumTouchPoints
370 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
371 touchPoints.length >= minimumTouchPoints &&
372 touchPoints.length <= maximumTouchPoints
373 property bool wasPressed: false
375 onRecognisedPressChanged: {
376 if (recognisedPress) {
382 if (status !== TouchGestureArea.Recognized) {
383 if (status === TouchGestureArea.WaitingForTouch) {
384 if (wasPressed && !dragging) {
385 launcher.toggleDrawer(true);
396 objectName: "inputMethod"
399 topMargin: panel.panelHeight
400 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
402 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
407 objectName: "greeterLoader"
410 if (shell.mode != "shell") {
411 if (screenWindow.primary) return integratedGreeter;
412 return secondaryGreeter;
414 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
417 item.objectName = "greeter"
419 property bool toggleDrawerAfterUnlock: false
426 // Show drawer in case showHome() requests it
427 if (greeterLoader.toggleDrawerAfterUnlock) {
428 launcher.toggleDrawer(false);
429 greeterLoader.toggleDrawerAfterUnlock = false;
438 id: integratedGreeter
441 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
442 hides: [launcher, panel.indicators, panel.applicationMenus]
443 tabletMode: shell.usageScenario != "phone"
444 usageMode: shell.usageScenario
445 orientation: shell.orientation
446 forcedUnlock: wizard.active || shell.mode === "full-shell"
447 background: wallpaperResolver.background
448 backgroundSourceSize: shell.largestScreenDimension
449 hasCustomBackground: wallpaperResolver.hasCustomBackground
450 inputMethodRect: inputMethod.visibleRect
451 hasKeyboard: shell.hasKeyboard
452 allowFingerprint: !dialogs.hasActiveDialog &&
453 !notifications.topmostIsFullscreen &&
454 !panel.indicators.shown
455 panelHeight: panel.panelHeight
457 // avoid overlapping with Launcher's edge drag area
458 // FIXME: Fix TouchRegistry & friends and remove this workaround
459 // Issue involves launcher's DDA getting disabled on a long
461 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
464 if (!tutorial.running) {
469 onEmergencyCall: startLockedApp("dialer-app")
476 hides: [launcher, panel.indicators]
481 // See powerConnection for why this is useful
482 id: showGreeterDelayed
485 // Go through the dbus service, because it has checks for whether
486 // we are even allowed to lock or not.
487 DBusLomiriSessionService.PromptLock();
496 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
497 // We just received an incoming call while locked. The
498 // indicator will have already launched dialer-app for us, but
499 // there is a race between "hasCalls" changing and the dialer
500 // starting up. So in case we lose that race, we'll start/
501 // focus the dialer ourselves here too. Even if the indicator
502 // didn't launch the dialer for some reason (or maybe a call
503 // started via some other means), if an active call is
504 // happening, we want to be in the dialer.
505 startLockedApp("dialer-app")
515 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
516 !callManager.hasCalls && !wizard.active) {
517 // We don't want to simply call greeter.showNow() here, because
518 // that will take too long. Qt will delay button event
519 // handling until the greeter is done loading and may think the
520 // user held down the power button the whole time, leading to a
521 // power dialog being shown. Instead, delay showing the
522 // greeter until we've finished handling the event. We could
523 // make the greeter load asynchronously instead, but that
524 // introduces a whole host of timing issues, especially with
525 // its animations. So this is simpler.
526 showGreeterDelayed.start();
531 function showHome() {
532 greeter.notifyUserRequestedApp();
534 if (shell.mode === "greeter") {
535 SessionBroadcast.requestHomeShown(AccountsService.user);
537 if (!greeter.active) {
538 launcher.toggleDrawer(false);
540 greeterLoader.toggleDrawerAfterUnlock = true;
554 anchors.fill: parent //because this draws indicator menus
555 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
557 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
558 minimizedPanelHeight: units.gu(3)
559 expandedPanelHeight: units.gu(7)
560 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
564 available: tutorial.panelEnabled
565 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
566 && (!greeter || !greeter.hasLockedApp)
567 && !shell.waitingOnGreeter
568 && settings.enableIndicatorMenu
570 model: Indicators.IndicatorsModel {
572 // tablet and phone both use the same profile
573 // FIXME: use just "phone" for greeter too, but first fix
574 // greeter app launching to either load the app inside the
575 // greeter or tell the session to load the app. This will
576 // involve taking the url-dispatcher dbus name and using
577 // SessionBroadcast to tell the session.
578 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
579 Component.onCompleted: {
587 available: (!greeter || !greeter.shown)
588 && !shell.waitingOnGreeter
589 && !stage.spreadShown
592 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
593 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
595 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
596 || greeter.hasLockedApp
597 greeterShown: greeter && greeter.shown
598 hasKeyboard: shell.hasKeyboard
599 panelState: panelState
600 supportsMultiColorLed: shell.supportsMultiColorLed
605 objectName: "launcher"
607 anchors.top: parent.top
608 anchors.topMargin: inverted ? 0 : panel.panelHeight
609 anchors.bottom: parent.bottom
611 dragAreaWidth: shell.edgeSize
612 available: tutorial.launcherEnabled
613 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
614 && !greeter.hasLockedApp
615 && !shell.waitingOnGreeter
616 inverted: shell.usageScenario !== "desktop"
617 superPressed: physicalKeysMapper.superPressed
618 superTabPressed: physicalKeysMapper.superTabPressed
619 panelWidth: units.gu(settings.launcherWidth)
620 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
621 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
622 topPanelHeight: panel.panelHeight
623 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
624 privateMode: greeter.active
625 background: wallpaperResolver.background
627 // It can be assumed that the Launcher and Panel would overlap if
628 // the Panel is open and taking up the full width of the shell
629 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
631 // The "autohideLauncher" setting is only valid in desktop mode
632 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
634 // The Launcher should absolutely not be locked visible under some
636 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
638 onShowDashHome: showHome()
639 onLauncherApplicationSelected: {
640 greeter.notifyUserRequestedApp();
641 shell.activateApplication(appId);
645 panel.indicators.hide();
646 panel.applicationMenus.hide();
649 onDrawerShownChanged: {
651 panel.indicators.hide();
652 panel.applicationMenus.hide();
662 shortcut: Qt.MetaModifier | Qt.Key_A
664 launcher.toggleDrawer(true);
668 shortcut: Qt.AltModifier | Qt.Key_F1
670 launcher.openForKeyboardNavigation();
674 shortcut: Qt.MetaModifier | Qt.Key_0
676 if (LauncherModel.get(9)) {
677 activateApplication(LauncherModel.get(9).appId);
684 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
686 if (LauncherModel.get(index)) {
687 activateApplication(LauncherModel.get(index).appId);
694 KeyboardShortcutsOverlay {
695 objectName: "shortcutsOverlay"
696 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
697 && height < parent.height - padding - panel.panelHeight
698 anchors.centerIn: parent
699 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
700 anchors.verticalCenterOffset: panel.panelHeight/2
702 opacity: enabled ? 0.95 : 0
704 Behavior on opacity {
705 LomiriNumberAnimation {}
711 objectName: "tutorial"
714 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
715 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
716 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
717 inputMethod.visible ||
718 (launcher.shown && !launcher.lockedVisible) ||
719 panel.indicators.shown || stage.rightEdgeDragProgress > 0
720 usageScenario: shell.usageScenario
721 lastInputTimestamp: inputFilter.lastInputTimestamp
731 deferred: shell.mode === "greeter"
733 function unlockWhenDoneWithWizard() {
735 ModemConnectivity.unlockAllModems();
739 Component.onCompleted: unlockWhenDoneWithWizard()
740 onActiveChanged: unlockWhenDoneWithWizard()
743 MouseArea { // modal notifications prevent interacting with other contents
745 visible: notifications.useModal
752 model: NotificationBackend.Model
754 hasMouse: shell.hasMouse
755 background: wallpaperResolver.background
756 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
758 y: topmostIsFullscreen ? 0 : panel.panelHeight
759 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
764 when: overlay.width <= units.gu(60)
766 target: notifications
767 anchors.left: parent.left
768 anchors.right: parent.right
773 when: overlay.width > units.gu(60)
775 target: notifications
776 anchors.left: undefined
777 anchors.right: parent.right
779 PropertyChanges { target: notifications; width: units.gu(38) }
786 enabled: !greeter.shown
788 // NB: it does its own positioning according to the specified edge
792 panel.indicators.hide()
795 material: Component {
801 anchors.centerIn: parent
803 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
804 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
814 objectName: "dialogs"
816 visible: hasActiveDialog
818 usageScenario: shell.usageScenario
819 hasKeyboard: shell.hasKeyboard
821 shutdownFadeOutRectangle.enabled = true;
822 shutdownFadeOutRectangle.visible = true;
823 shutdownFadeOut.start();
828 target: SessionBroadcast
829 onShowHome: if (shell.mode !== "greeter") showHome()
834 objectName: "urlDispatcher"
835 active: shell.mode === "greeter"
836 onUrlRequested: shell.activateURL(url)
843 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
846 ignoreUnknownSignals: true
847 onItemSnapshotRequested: itemGrabber.capture(item)
852 id: cursorHidingTimer
854 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
855 onTriggered: cursor.opacity = 0;
863 topBoundaryOffset: panel.panelHeight
864 enabled: shell.hasMouse && screenWindow.active
867 property bool mouseNeverMoved: true
869 target: cursor; property: "x"; value: shell.width / 2
870 when: cursor.mouseNeverMoved && cursor.visible
873 target: cursor; property: "y"; value: shell.height / 2
874 when: cursor.mouseNeverMoved && cursor.visible
877 confiningItem: stage.itemConfiningMouseCursor
881 readonly property var previewRectangle: stage.previewRectangle.target &&
882 stage.previewRectangle.target.dragging ?
883 stage.previewRectangle : null
885 onPushedLeftBoundary: {
886 if (buttons === Qt.NoButton) {
887 launcher.pushEdge(amount);
888 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
889 previewRectangle.maximizeLeft(amount);
893 onPushedRightBoundary: {
894 if (buttons === Qt.NoButton) {
895 rightEdgeBarrier.push(amount);
896 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
897 previewRectangle.maximizeRight(amount);
901 onPushedTopBoundary: {
902 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
903 previewRectangle.maximize(amount);
906 onPushedTopLeftCorner: {
907 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
908 previewRectangle.maximizeTopLeft(amount);
911 onPushedTopRightCorner: {
912 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
913 previewRectangle.maximizeTopRight(amount);
916 onPushedBottomLeftCorner: {
917 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
918 previewRectangle.maximizeBottomLeft(amount);
921 onPushedBottomRightCorner: {
922 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
923 previewRectangle.maximizeBottomRight(amount);
927 if (previewRectangle) {
928 previewRectangle.stop();
933 mouseNeverMoved = false;
937 Behavior on opacity { LomiriNumberAnimation {} }
940 // non-visual objects
942 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
947 id: shutdownFadeOutRectangle
954 NumberAnimation on opacity {
959 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
960 DBusLomiriSessionService.shutdown();