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 && shell.mode !== "greeter"
617 visible: shell.mode !== "greeter"
618 inverted: shell.usageScenario !== "desktop"
619 superPressed: physicalKeysMapper.superPressed
620 superTabPressed: physicalKeysMapper.superTabPressed
621 panelWidth: units.gu(settings.launcherWidth)
622 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
623 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
624 topPanelHeight: panel.panelHeight
625 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
626 privateMode: greeter.active
627 background: wallpaperResolver.background
629 // It can be assumed that the Launcher and Panel would overlap if
630 // the Panel is open and taking up the full width of the shell
631 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
633 // The "autohideLauncher" setting is only valid in desktop mode
634 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
636 // The Launcher should absolutely not be locked visible under some
638 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
640 onShowDashHome: showHome()
641 onLauncherApplicationSelected: {
642 greeter.notifyUserRequestedApp();
643 shell.activateApplication(appId);
647 panel.indicators.hide();
648 panel.applicationMenus.hide();
651 onDrawerShownChanged: {
653 panel.indicators.hide();
654 panel.applicationMenus.hide();
664 shortcut: Qt.MetaModifier | Qt.Key_A
666 launcher.toggleDrawer(true);
670 shortcut: Qt.AltModifier | Qt.Key_F1
672 launcher.openForKeyboardNavigation();
676 shortcut: Qt.MetaModifier | Qt.Key_0
678 if (LauncherModel.get(9)) {
679 activateApplication(LauncherModel.get(9).appId);
686 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
688 if (LauncherModel.get(index)) {
689 activateApplication(LauncherModel.get(index).appId);
696 KeyboardShortcutsOverlay {
697 objectName: "shortcutsOverlay"
698 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
699 && height < parent.height - padding - panel.panelHeight
700 anchors.centerIn: parent
701 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
702 anchors.verticalCenterOffset: panel.panelHeight/2
704 opacity: enabled ? 0.95 : 0
706 Behavior on opacity {
707 LomiriNumberAnimation {}
713 objectName: "tutorial"
716 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
717 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
718 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
719 inputMethod.visible ||
720 (launcher.shown && !launcher.lockedVisible) ||
721 panel.indicators.shown || stage.rightEdgeDragProgress > 0
722 usageScenario: shell.usageScenario
723 lastInputTimestamp: inputFilter.lastInputTimestamp
733 deferred: shell.mode === "greeter"
735 function unlockWhenDoneWithWizard() {
737 ModemConnectivity.unlockAllModems();
741 Component.onCompleted: unlockWhenDoneWithWizard()
742 onActiveChanged: unlockWhenDoneWithWizard()
745 MouseArea { // modal notifications prevent interacting with other contents
747 visible: notifications.useModal
754 model: NotificationBackend.Model
756 hasMouse: shell.hasMouse
757 background: wallpaperResolver.background
758 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
760 y: topmostIsFullscreen ? 0 : panel.panelHeight
761 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
766 when: overlay.width <= units.gu(60)
768 target: notifications
769 anchors.left: parent.left
770 anchors.right: parent.right
775 when: overlay.width > units.gu(60)
777 target: notifications
778 anchors.left: undefined
779 anchors.right: parent.right
781 PropertyChanges { target: notifications; width: units.gu(38) }
788 enabled: !greeter.shown
790 // NB: it does its own positioning according to the specified edge
794 panel.indicators.hide()
797 material: Component {
803 anchors.centerIn: parent
805 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
806 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
816 objectName: "dialogs"
818 visible: hasActiveDialog
820 usageScenario: shell.usageScenario
821 hasKeyboard: shell.hasKeyboard
823 shutdownFadeOutRectangle.enabled = true;
824 shutdownFadeOutRectangle.visible = true;
825 shutdownFadeOut.start();
830 target: SessionBroadcast
831 onShowHome: if (shell.mode !== "greeter") showHome()
836 objectName: "urlDispatcher"
837 active: shell.mode === "greeter"
838 onUrlRequested: shell.activateURL(url)
845 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
848 ignoreUnknownSignals: true
849 onItemSnapshotRequested: itemGrabber.capture(item)
854 id: cursorHidingTimer
856 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
857 onTriggered: cursor.opacity = 0;
865 topBoundaryOffset: panel.panelHeight
866 enabled: shell.hasMouse && screenWindow.active
869 property bool mouseNeverMoved: true
871 target: cursor; property: "x"; value: shell.width / 2
872 when: cursor.mouseNeverMoved && cursor.visible
875 target: cursor; property: "y"; value: shell.height / 2
876 when: cursor.mouseNeverMoved && cursor.visible
879 confiningItem: stage.itemConfiningMouseCursor
883 readonly property var previewRectangle: stage.previewRectangle.target &&
884 stage.previewRectangle.target.dragging ?
885 stage.previewRectangle : null
887 onPushedLeftBoundary: {
888 if (buttons === Qt.NoButton) {
889 launcher.pushEdge(amount);
890 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
891 previewRectangle.maximizeLeft(amount);
895 onPushedRightBoundary: {
896 if (buttons === Qt.NoButton) {
897 rightEdgeBarrier.push(amount);
898 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
899 previewRectangle.maximizeRight(amount);
903 onPushedTopBoundary: {
904 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
905 previewRectangle.maximize(amount);
908 onPushedTopLeftCorner: {
909 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
910 previewRectangle.maximizeTopLeft(amount);
913 onPushedTopRightCorner: {
914 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915 previewRectangle.maximizeTopRight(amount);
918 onPushedBottomLeftCorner: {
919 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920 previewRectangle.maximizeBottomLeft(amount);
923 onPushedBottomRightCorner: {
924 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
925 previewRectangle.maximizeBottomRight(amount);
929 if (previewRectangle) {
930 previewRectangle.stop();
935 mouseNeverMoved = false;
939 Behavior on opacity { LomiriNumberAnimation {} }
942 // non-visual objects
944 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
949 id: shutdownFadeOutRectangle
956 NumberAnimation on opacity {
961 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
962 DBusLomiriSessionService.shutdown();