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 || !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 (showingGreeter || 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);
231 function startLockedApp(app) {
232 topLevelSurfaceList.pendingActivation();
234 if (greeter.locked) {
235 greeter.lockedApp = app;
237 startApp(app); // locked apps are always in our same session
241 target: LauncherModel
242 property: "applicationManager"
243 value: ApplicationManager
246 Component.onCompleted: {
247 finishStartUpTimer.start();
255 id: physicalKeysMapper
256 objectName: "physicalKeysMapper"
258 onPowerKeyLongPressed: dialogs.showPowerDialog();
259 onVolumeDownTriggered: volumeControl.volumeDown();
260 onVolumeUpTriggered: volumeControl.volumeUp();
261 onScreenshotTriggered: itemGrabber.capture(shell);
265 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
270 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
271 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
275 objectName: "windowInputMonitor"
276 onHomeKeyActivated: {
277 // Ignore when greeter is active, to avoid pocket presses
278 if (!greeter.active) {
279 launcher.toggleDrawer(/* focusInputField */ false,
280 /* onlyOpen */ false,
281 /* alsoToggleLauncher */ true);
284 onTouchBegun: { cursor.opacity = 0; }
286 // move the (hidden) cursor to the last known touch position
287 var mappedCoords = mapFromItem(null, pos.x, pos.y);
288 cursor.x = mappedCoords.x;
289 cursor.y = mappedCoords.y;
290 cursor.mouseNeverMoved = false;
294 AvailableDesktopArea {
295 id: availableDesktopAreaItem
297 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
298 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
303 schema.id: "com.lomiri.Shell"
308 objectName: "panelState"
315 height: parent.height
323 dragAreaWidth: shell.edgeSize
324 background: wallpaperResolver.background
325 backgroundSourceSize: shell.largestScreenDimension
327 applicationManager: ApplicationManager
328 topLevelSurfaceList: shell.topLevelSurfaceList
329 inputMethodRect: inputMethod.visibleRect
330 rightEdgePushProgress: rightEdgeBarrier.progress
331 availableDesktopArea: availableDesktopAreaItem
332 launcherLeftMargin: launcher.visibleWidth
334 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
336 : shell.usageScenario
338 mode: usageScenario == "phone" ? "staged"
339 : usageScenario == "tablet" ? "stagedWithSideStage"
342 shellOrientation: shell.orientation
343 shellOrientationAngle: shell.orientationAngle
344 orientations: shell.orientations
345 nativeWidth: shell.nativeWidth
346 nativeHeight: shell.nativeHeight
348 allowInteractivity: (!greeter || !greeter.shown)
349 && panel.indicators.fullyClosed
350 && !notifications.useModal
351 && !launcher.takesFocus
353 suspended: greeter.shown
354 altTabPressed: physicalKeysMapper.altTabPressed
355 oskEnabled: shell.oskEnabled
356 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
357 panelState: panelState
359 onSpreadShownChanged: {
360 panel.indicators.hide();
361 panel.applicationMenus.hide();
368 minimumTouchPoints: 4
369 maximumTouchPoints: minimumTouchPoints
371 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
372 touchPoints.length >= minimumTouchPoints &&
373 touchPoints.length <= maximumTouchPoints
374 property bool wasPressed: false
376 onRecognisedPressChanged: {
377 if (recognisedPress) {
383 if (status !== TouchGestureArea.Recognized) {
384 if (status === TouchGestureArea.WaitingForTouch) {
385 if (wasPressed && !dragging) {
386 launcher.toggleDrawer(true);
397 objectName: "inputMethod"
400 topMargin: panel.panelHeight
401 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
403 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
408 objectName: "greeterLoader"
411 if (shell.mode != "shell") {
412 if (screenWindow.primary) return integratedGreeter;
413 return secondaryGreeter;
415 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
418 item.objectName = "greeter"
420 property bool toggleDrawerAfterUnlock: false
427 // Show drawer in case showHome() requests it
428 if (greeterLoader.toggleDrawerAfterUnlock) {
429 launcher.toggleDrawer(false);
430 greeterLoader.toggleDrawerAfterUnlock = false;
439 id: integratedGreeter
442 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
443 hides: [launcher, panel.indicators, panel.applicationMenus]
444 tabletMode: shell.usageScenario != "phone"
445 forcedUnlock: wizard.active || shell.mode === "full-shell"
446 background: wallpaperResolver.background
447 backgroundSourceSize: shell.largestScreenDimension
448 hasCustomBackground: wallpaperResolver.hasCustomBackground
449 inputMethodRect: inputMethod.visibleRect
450 hasKeyboard: shell.hasKeyboard
451 allowFingerprint: !dialogs.hasActiveDialog &&
452 !notifications.topmostIsFullscreen &&
453 !panel.indicators.shown
454 panelHeight: panel.panelHeight
456 // avoid overlapping with Launcher's edge drag area
457 // FIXME: Fix TouchRegistry & friends and remove this workaround
458 // Issue involves launcher's DDA getting disabled on a long
460 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
463 if (!tutorial.running) {
468 onEmergencyCall: startLockedApp("dialer-app")
475 hides: [launcher, panel.indicators]
480 // See powerConnection for why this is useful
481 id: showGreeterDelayed
484 // Go through the dbus service, because it has checks for whether
485 // we are even allowed to lock or not.
486 DBusLomiriSessionService.PromptLock();
495 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
496 // We just received an incoming call while locked. The
497 // indicator will have already launched dialer-app for us, but
498 // there is a race between "hasCalls" changing and the dialer
499 // starting up. So in case we lose that race, we'll start/
500 // focus the dialer ourselves here too. Even if the indicator
501 // didn't launch the dialer for some reason (or maybe a call
502 // started via some other means), if an active call is
503 // happening, we want to be in the dialer.
504 startLockedApp("dialer-app")
514 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
515 !callManager.hasCalls && !wizard.active) {
516 // We don't want to simply call greeter.showNow() here, because
517 // that will take too long. Qt will delay button event
518 // handling until the greeter is done loading and may think the
519 // user held down the power button the whole time, leading to a
520 // power dialog being shown. Instead, delay showing the
521 // greeter until we've finished handling the event. We could
522 // make the greeter load asynchronously instead, but that
523 // introduces a whole host of timing issues, especially with
524 // its animations. So this is simpler.
525 showGreeterDelayed.start();
530 function showHome() {
531 greeter.notifyUserRequestedApp();
533 if (shell.mode === "greeter") {
534 SessionBroadcast.requestHomeShown(AccountsService.user);
536 if (!greeter.active) {
537 launcher.toggleDrawer(false);
539 greeterLoader.toggleDrawerAfterUnlock = true;
553 anchors.fill: parent //because this draws indicator menus
554 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
556 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
557 minimizedPanelHeight: units.gu(3)
558 expandedPanelHeight: units.gu(7)
559 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
563 available: tutorial.panelEnabled
564 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
565 && (!greeter || !greeter.hasLockedApp)
566 && !shell.waitingOnGreeter
567 && settings.enableIndicatorMenu
569 model: Indicators.IndicatorsModel {
571 // tablet and phone both use the same profile
572 // FIXME: use just "phone" for greeter too, but first fix
573 // greeter app launching to either load the app inside the
574 // greeter or tell the session to load the app. This will
575 // involve taking the url-dispatcher dbus name and using
576 // SessionBroadcast to tell the session.
577 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
578 Component.onCompleted: {
586 available: (!greeter || !greeter.shown)
587 && !shell.waitingOnGreeter
588 && !stage.spreadShown
591 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
592 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
594 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
595 || greeter.hasLockedApp
596 greeterShown: greeter && greeter.shown
597 hasKeyboard: shell.hasKeyboard
598 panelState: panelState
599 supportsMultiColorLed: shell.supportsMultiColorLed
604 objectName: "launcher"
606 anchors.top: parent.top
607 anchors.topMargin: inverted ? 0 : panel.panelHeight
608 anchors.bottom: parent.bottom
610 dragAreaWidth: shell.edgeSize
611 available: tutorial.launcherEnabled
612 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
613 && !greeter.hasLockedApp
614 && !shell.waitingOnGreeter
615 inverted: shell.usageScenario !== "desktop"
616 superPressed: physicalKeysMapper.superPressed
617 superTabPressed: physicalKeysMapper.superTabPressed
618 panelWidth: units.gu(settings.launcherWidth)
619 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
620 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
621 topPanelHeight: panel.panelHeight
622 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
623 privateMode: greeter.active
624 background: wallpaperResolver.background
626 // It can be assumed that the Launcher and Panel would overlap if
627 // the Panel is open and taking up the full width of the shell
628 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
630 // The "autohideLauncher" setting is only valid in desktop mode
631 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
633 // The Launcher should absolutely not be locked visible under some
635 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
637 onShowDashHome: showHome()
638 onLauncherApplicationSelected: {
639 greeter.notifyUserRequestedApp();
640 shell.activateApplication(appId);
644 panel.indicators.hide();
645 panel.applicationMenus.hide();
648 onDrawerShownChanged: {
650 panel.indicators.hide();
651 panel.applicationMenus.hide();
661 shortcut: Qt.MetaModifier | Qt.Key_A
663 launcher.toggleDrawer(true);
667 shortcut: Qt.AltModifier | Qt.Key_F1
669 launcher.openForKeyboardNavigation();
673 shortcut: Qt.MetaModifier | Qt.Key_0
675 if (LauncherModel.get(9)) {
676 activateApplication(LauncherModel.get(9).appId);
683 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
685 if (LauncherModel.get(index)) {
686 activateApplication(LauncherModel.get(index).appId);
693 KeyboardShortcutsOverlay {
694 objectName: "shortcutsOverlay"
695 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
696 && height < parent.height - padding - panel.panelHeight
697 anchors.centerIn: parent
698 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
699 anchors.verticalCenterOffset: panel.panelHeight/2
701 opacity: enabled ? 0.95 : 0
703 Behavior on opacity {
704 LomiriNumberAnimation {}
710 objectName: "tutorial"
713 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
714 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
715 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
716 inputMethod.visible ||
717 (launcher.shown && !launcher.lockedVisible) ||
718 panel.indicators.shown || stage.rightEdgeDragProgress > 0
719 usageScenario: shell.usageScenario
720 lastInputTimestamp: inputFilter.lastInputTimestamp
730 deferred: shell.mode === "greeter"
732 function unlockWhenDoneWithWizard() {
734 ModemConnectivity.unlockAllModems();
738 Component.onCompleted: unlockWhenDoneWithWizard()
739 onActiveChanged: unlockWhenDoneWithWizard()
742 MouseArea { // modal notifications prevent interacting with other contents
744 visible: notifications.useModal
751 model: NotificationBackend.Model
753 hasMouse: shell.hasMouse
754 background: wallpaperResolver.background
756 y: topmostIsFullscreen ? 0 : panel.panelHeight
757 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
762 when: overlay.width <= units.gu(60)
764 target: notifications
765 anchors.left: parent.left
766 anchors.right: parent.right
771 when: overlay.width > units.gu(60)
773 target: notifications
774 anchors.left: undefined
775 anchors.right: parent.right
777 PropertyChanges { target: notifications; width: units.gu(38) }
784 enabled: !greeter.shown
786 // NB: it does its own positioning according to the specified edge
790 panel.indicators.hide()
793 material: Component {
799 anchors.centerIn: parent
801 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
802 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
812 objectName: "dialogs"
814 visible: hasActiveDialog
816 usageScenario: shell.usageScenario
817 hasKeyboard: shell.hasKeyboard
819 shutdownFadeOutRectangle.enabled = true;
820 shutdownFadeOutRectangle.visible = true;
821 shutdownFadeOut.start();
826 target: SessionBroadcast
827 onShowHome: if (shell.mode !== "greeter") showHome()
832 objectName: "urlDispatcher"
833 active: shell.mode === "greeter"
834 onUrlRequested: shell.activateURL(url)
841 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
844 ignoreUnknownSignals: true
845 onItemSnapshotRequested: itemGrabber.capture(item)
850 id: cursorHidingTimer
852 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
853 onTriggered: cursor.opacity = 0;
861 topBoundaryOffset: panel.panelHeight
862 enabled: shell.hasMouse && screenWindow.active
865 property bool mouseNeverMoved: true
867 target: cursor; property: "x"; value: shell.width / 2
868 when: cursor.mouseNeverMoved && cursor.visible
871 target: cursor; property: "y"; value: shell.height / 2
872 when: cursor.mouseNeverMoved && cursor.visible
875 confiningItem: stage.itemConfiningMouseCursor
879 readonly property var previewRectangle: stage.previewRectangle.target &&
880 stage.previewRectangle.target.dragging ?
881 stage.previewRectangle : null
883 onPushedLeftBoundary: {
884 if (buttons === Qt.NoButton) {
885 launcher.pushEdge(amount);
886 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
887 previewRectangle.maximizeLeft(amount);
891 onPushedRightBoundary: {
892 if (buttons === Qt.NoButton) {
893 rightEdgeBarrier.push(amount);
894 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
895 previewRectangle.maximizeRight(amount);
899 onPushedTopBoundary: {
900 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
901 previewRectangle.maximize(amount);
904 onPushedTopLeftCorner: {
905 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
906 previewRectangle.maximizeTopLeft(amount);
909 onPushedTopRightCorner: {
910 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
911 previewRectangle.maximizeTopRight(amount);
914 onPushedBottomLeftCorner: {
915 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
916 previewRectangle.maximizeBottomLeft(amount);
919 onPushedBottomRightCorner: {
920 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
921 previewRectangle.maximizeBottomRight(amount);
925 if (previewRectangle) {
926 previewRectangle.stop();
931 mouseNeverMoved = false;
935 Behavior on opacity { LomiriNumberAnimation {} }
938 // non-visual objects
940 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
945 id: shutdownFadeOutRectangle
952 NumberAnimation on opacity {
957 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
958 DBusLomiriSessionService.shutdown();