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 readonly property bool lightMode: settings.lightMode
53 theme.name: lightMode ? "Lomiri.Components.Themes.Ambiance" :
54 "Lomiri.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 panelAreaShowProgress: panel.panelAreaShowProgress
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
73 property bool hasKeyboard: false
74 property bool hasTouchscreen: false
75 property bool supportsMultiColorLed: true
77 // The largest dimension, in pixels, of all of the screens this Shell is
79 // If a script sets the shell to 240x320 when it was 320x240, we could
80 // end up in a situation where our dimensions are 240x240 for a short time.
81 // Notifying the Wallpaper of both events would make it reload the image
82 // twice. So, we use a Binding { delayed: true }.
83 property real largestScreenDimension
87 property: "largestScreenDimension"
88 value: Math.max(nativeWidth, nativeHeight)
92 property alias lightIndicators: indicatorsModel.light
94 // to be read from outside
95 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
97 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
98 && stage.orientationChangesEnabled
99 && (!greeter.animating)
101 readonly property bool showingGreeter: greeter && greeter.shown
103 property bool startingUp: true
104 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
106 property int supportedOrientations: {
108 // Ensure we don't rotate during start up
109 return Qt.PrimaryOrientation;
110 } else if (notifications.topmostIsFullscreen) {
111 return Qt.PrimaryOrientation;
113 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
117 readonly property var mainApp: stage.mainApp
119 readonly property var topLevelSurfaceList: {
120 if (!WMScreen.currentWorkspace) return null;
121 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
125 _onMainAppChanged((mainApp ? mainApp.appId : ""));
128 target: ApplicationManager
130 if (shell.mainApp && shell.mainApp.appId === appId) {
131 _onMainAppChanged(appId);
136 // Calls attention back to the most important thing that's been focused
137 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
138 // goes over everything if it is locked)
139 // Must be called whenever app focus changes occur, even if the focus change
140 // is "nothing is focused". In that case, call with appId = ""
141 function _onMainAppChanged(appId) {
145 // If this happens on first boot, we may be in the
146 // wizard while receiving a call. A call is more
147 // important than the wizard so just bail out of it.
151 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
152 // If we are in the middle of a call, make dialer lockedApp. The
153 // Greeter will show it when it's notified of the focus.
154 // This can happen if user backs out of dialer back to greeter, then
155 // launches dialer again.
156 greeter.lockedApp = appId;
159 panel.indicators.hide();
160 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
163 // *Always* make sure the greeter knows that the focused app changed
164 if (greeter) greeter.notifyAppFocusRequested(appId);
167 // For autopilot consumption
168 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
170 // Note when greeter is waiting on PAM, so that we can disable edges until
171 // we know which user data to show and whether the session is locked.
172 readonly property bool waitingOnGreeter: greeter && greeter.waiting
174 // True when the user is logged in with no apps running
175 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
177 onAtDesktopChanged: {
178 if (atDesktop && stage) {
183 property real edgeSize: units.gu(settings.edgeDragWidth)
186 id: wallpaperResolver
187 objectName: "wallpaperResolver"
189 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
190 readonly property bool hasCustomBackground: background != defaultBackground
193 id: backgroundSettings
194 schema.id: ((shell.showingGreeter == true) || (shell.mode === "full-greeter") || (shell.mode === "greeter")) ? "com.lomiri.Shell.Greeter" : "com.lomiri.Shell"
198 AccountsService.backgroundFile,
199 backgroundSettings.backgroundPictureUri,
204 readonly property alias greeter: greeterLoader.item
206 function activateApplication(appId) {
207 topLevelSurfaceList.pendingActivation();
209 // Either open the app in our own session, or -- if we're acting as a
210 // greeter -- ask the user's session to open it for us.
211 if (shell.mode === "greeter") {
212 activateURL("application:///" + appId + ".desktop");
219 function activateURL(url) {
220 SessionBroadcast.requestUrlStart(AccountsService.user, url);
221 greeter.notifyUserRequestedApp();
222 panel.indicators.hide();
225 function startApp(appId) {
226 if (!ApplicationManager.findApplication(appId)) {
227 ApplicationManager.startApplication(appId);
229 ApplicationManager.requestFocusApplication(appId);
232 function startLockedApp(app) {
233 topLevelSurfaceList.pendingActivation();
235 if (greeter.locked) {
236 greeter.lockedApp = app;
238 startApp(app); // locked apps are always in our same session
242 target: LauncherModel
243 property: "applicationManager"
244 value: ApplicationManager
247 Component.onCompleted: {
248 finishStartUpTimer.start();
256 id: physicalKeysMapper
257 objectName: "physicalKeysMapper"
259 onPowerKeyLongPressed: dialogs.showPowerDialog();
260 onVolumeDownTriggered: volumeControl.volumeDown();
261 onVolumeUpTriggered: volumeControl.volumeUp();
262 onScreenshotTriggered: itemGrabber.capture(shell);
266 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
271 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
272 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
276 objectName: "windowInputMonitor"
277 onHomeKeyActivated: {
278 // Ignore when greeter is active, to avoid pocket presses
279 if (!greeter.active) {
280 launcher.toggleDrawer(/* focusInputField */ false,
281 /* onlyOpen */ false,
282 /* alsoToggleLauncher */ true);
285 onTouchBegun: { cursor.opacity = 0; }
287 // move the (hidden) cursor to the last known touch position
288 var mappedCoords = mapFromItem(null, pos.x, pos.y);
289 cursor.x = mappedCoords.x;
290 cursor.y = mappedCoords.y;
291 cursor.mouseNeverMoved = false;
295 AvailableDesktopArea {
296 id: availableDesktopAreaItem
298 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
299 anchors.leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
304 schema.id: "com.lomiri.Shell"
309 objectName: "panelState"
316 height: parent.height
323 lightMode: shell.lightMode
325 dragAreaWidth: shell.edgeSize
326 background: wallpaperResolver.background
327 backgroundSourceSize: shell.largestScreenDimension
329 applicationManager: ApplicationManager
330 topLevelSurfaceList: shell.topLevelSurfaceList
331 inputMethodRect: inputMethod.visibleRect
332 rightEdgePushProgress: rightEdgeBarrier.progress
333 availableDesktopArea: availableDesktopAreaItem
334 launcherLeftMargin: launcher.visibleWidth
336 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
338 : shell.usageScenario
340 mode: usageScenario == "phone" ? "staged"
341 : usageScenario == "tablet" ? "stagedWithSideStage"
344 shellOrientation: shell.orientation
345 shellOrientationAngle: shell.orientationAngle
346 orientations: shell.orientations
347 nativeWidth: shell.nativeWidth
348 nativeHeight: shell.nativeHeight
350 allowInteractivity: (!greeter || !greeter.shown)
351 && panel.indicators.fullyClosed
352 && !notifications.useModal
353 && !launcher.takesFocus
355 suspended: greeter.shown
356 altTabPressed: physicalKeysMapper.altTabPressed
357 oskEnabled: shell.oskEnabled
358 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
359 panelState: panelState
361 onSpreadShownChanged: {
362 panel.indicators.hide();
363 panel.applicationMenus.hide();
370 minimumTouchPoints: 4
371 maximumTouchPoints: minimumTouchPoints
373 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
374 touchPoints.length >= minimumTouchPoints &&
375 touchPoints.length <= maximumTouchPoints
376 property bool wasPressed: false
378 onRecognisedPressChanged: {
379 if (recognisedPress) {
385 if (status !== TouchGestureArea.Recognized) {
386 if (status === TouchGestureArea.WaitingForTouch) {
387 if (wasPressed && !dragging) {
388 launcher.toggleDrawer(true);
399 objectName: "inputMethod"
402 topMargin: panel.panelHeight
403 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
405 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
410 objectName: "greeterLoader"
413 if (shell.mode != "shell") {
414 if (screenWindow.primary) return integratedGreeter;
415 return secondaryGreeter;
417 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
420 item.objectName = "greeter"
422 property bool toggleDrawerAfterUnlock: false
429 // Show drawer in case showHome() requests it
430 if (greeterLoader.toggleDrawerAfterUnlock) {
431 launcher.toggleDrawer(false);
432 greeterLoader.toggleDrawerAfterUnlock = false;
441 id: integratedGreeter
444 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
445 hides: [launcher, panel.indicators, panel.applicationMenus]
446 tabletMode: shell.usageScenario != "phone"
447 usageMode: shell.usageScenario
448 orientation: shell.orientation
449 forcedUnlock: wizard.active || shell.mode === "full-shell"
450 background: wallpaperResolver.background
451 backgroundSourceSize: shell.largestScreenDimension
452 hasCustomBackground: wallpaperResolver.hasCustomBackground
453 inputMethodRect: inputMethod.visibleRect
454 hasKeyboard: shell.hasKeyboard
455 allowFingerprint: !dialogs.hasActiveDialog &&
456 !notifications.topmostIsFullscreen &&
457 !panel.indicators.shown
458 panelHeight: panel.panelHeight
460 // avoid overlapping with Launcher's edge drag area
461 // FIXME: Fix TouchRegistry & friends and remove this workaround
462 // Issue involves launcher's DDA getting disabled on a long
464 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
467 if (!tutorial.running) {
472 onEmergencyCall: startLockedApp("dialer-app")
479 hides: [launcher, panel.indicators]
484 // See powerConnection for why this is useful
485 id: showGreeterDelayed
488 // Go through the dbus service, because it has checks for whether
489 // we are even allowed to lock or not.
490 DBusLomiriSessionService.PromptLock();
499 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
500 // We just received an incoming call while locked. The
501 // indicator will have already launched dialer-app for us, but
502 // there is a race between "hasCalls" changing and the dialer
503 // starting up. So in case we lose that race, we'll start/
504 // focus the dialer ourselves here too. Even if the indicator
505 // didn't launch the dialer for some reason (or maybe a call
506 // started via some other means), if an active call is
507 // happening, we want to be in the dialer.
508 startLockedApp("dialer-app")
518 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
519 !callManager.hasCalls && !wizard.active) {
520 // We don't want to simply call greeter.showNow() here, because
521 // that will take too long. Qt will delay button event
522 // handling until the greeter is done loading and may think the
523 // user held down the power button the whole time, leading to a
524 // power dialog being shown. Instead, delay showing the
525 // greeter until we've finished handling the event. We could
526 // make the greeter load asynchronously instead, but that
527 // introduces a whole host of timing issues, especially with
528 // its animations. So this is simpler.
529 showGreeterDelayed.start();
534 function showHome() {
535 greeter.notifyUserRequestedApp();
537 if (shell.mode === "greeter") {
538 SessionBroadcast.requestHomeShown(AccountsService.user);
540 if (!greeter.active) {
541 launcher.toggleDrawer(false);
543 greeterLoader.toggleDrawerAfterUnlock = true;
557 anchors.fill: parent //because this draws indicator menus
558 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
559 lightMode: shell.lightMode
561 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
562 minimizedPanelHeight: units.gu(3)
563 expandedPanelHeight: units.gu(7)
564 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
568 available: tutorial.panelEnabled
569 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
570 && (!greeter || !greeter.hasLockedApp)
571 && !shell.waitingOnGreeter
572 && settings.enableIndicatorMenu
574 model: Indicators.IndicatorsModel {
576 // tablet and phone both use the same profile
577 // FIXME: use just "phone" for greeter too, but first fix
578 // greeter app launching to either load the app inside the
579 // greeter or tell the session to load the app. This will
580 // involve taking the url-dispatcher dbus name and using
581 // SessionBroadcast to tell the session.
582 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
583 Component.onCompleted: {
591 available: (!greeter || !greeter.shown)
592 && !shell.waitingOnGreeter
593 && !stage.spreadShown
596 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
597 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
599 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
600 || greeter.hasLockedApp
601 greeterShown: greeter && greeter.shown
602 hasKeyboard: shell.hasKeyboard
603 panelState: panelState
604 supportsMultiColorLed: shell.supportsMultiColorLed
609 objectName: "launcher"
611 anchors.top: parent.top
612 anchors.topMargin: inverted ? 0 : panel.panelHeight
613 anchors.bottom: parent.bottom
615 dragAreaWidth: shell.edgeSize
616 available: tutorial.launcherEnabled
617 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
618 && !greeter.hasLockedApp
619 && !shell.waitingOnGreeter
620 && shell.mode !== "greeter"
621 visible: shell.mode !== "greeter"
622 inverted: shell.usageScenario !== "desktop"
623 superPressed: physicalKeysMapper.superPressed
624 superTabPressed: physicalKeysMapper.superTabPressed
625 panelWidth: units.gu(settings.launcherWidth)
626 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
627 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
628 topPanelHeight: panel.panelHeight
629 lightMode: shell.lightMode
630 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
631 privateMode: greeter.active
632 background: wallpaperResolver.background
634 // It can be assumed that the Launcher and Panel would overlap if
635 // the Panel is open and taking up the full width of the shell
636 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
638 // The "autohideLauncher" setting is only valid in desktop mode
639 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
641 // The Launcher should absolutely not be locked visible under some
643 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
645 onShowDashHome: showHome()
646 onLauncherApplicationSelected: {
647 greeter.notifyUserRequestedApp();
648 shell.activateApplication(appId);
652 panel.indicators.hide();
653 panel.applicationMenus.hide();
656 onDrawerShownChanged: {
658 panel.indicators.hide();
659 panel.applicationMenus.hide();
669 shortcut: Qt.MetaModifier | Qt.Key_A
671 launcher.toggleDrawer(true);
675 shortcut: Qt.AltModifier | Qt.Key_F1
677 launcher.openForKeyboardNavigation();
681 shortcut: Qt.MetaModifier | Qt.Key_0
683 if (LauncherModel.get(9)) {
684 activateApplication(LauncherModel.get(9).appId);
691 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
693 if (LauncherModel.get(index)) {
694 activateApplication(LauncherModel.get(index).appId);
701 KeyboardShortcutsOverlay {
702 objectName: "shortcutsOverlay"
703 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
704 && height < parent.height - padding - panel.panelHeight
705 anchors.centerIn: parent
706 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
707 anchors.verticalCenterOffset: panel.panelHeight/2
709 opacity: enabled ? 0.95 : 0
711 Behavior on opacity {
712 LomiriNumberAnimation {}
718 objectName: "tutorial"
721 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
722 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
723 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
724 inputMethod.visible ||
725 (launcher.shown && !launcher.lockedVisible) ||
726 panel.indicators.shown || stage.rightEdgeDragProgress > 0
727 usageScenario: shell.usageScenario
728 lastInputTimestamp: inputFilter.lastInputTimestamp
738 deferred: shell.mode === "greeter"
740 function unlockWhenDoneWithWizard() {
742 ModemConnectivity.unlockAllModems();
746 Component.onCompleted: unlockWhenDoneWithWizard()
747 onActiveChanged: unlockWhenDoneWithWizard()
750 MouseArea { // modal notifications prevent interacting with other contents
752 visible: notifications.useModal
759 model: NotificationBackend.Model
761 hasMouse: shell.hasMouse
762 background: wallpaperResolver.background
763 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
765 y: topmostIsFullscreen ? 0 : panel.panelHeight
766 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
771 when: overlay.width <= units.gu(60)
773 target: notifications
774 anchors.left: parent.left
775 anchors.right: parent.right
780 when: overlay.width > units.gu(60)
782 target: notifications
783 anchors.left: undefined
784 anchors.right: parent.right
786 PropertyChanges { target: notifications; width: units.gu(38) }
793 enabled: !greeter.shown
795 // NB: it does its own positioning according to the specified edge
799 panel.indicators.hide()
802 material: Component {
808 anchors.centerIn: parent
810 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
811 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
821 objectName: "dialogs"
823 visible: hasActiveDialog
825 usageScenario: shell.usageScenario
826 hasKeyboard: shell.hasKeyboard
828 shutdownFadeOutRectangle.enabled = true;
829 shutdownFadeOutRectangle.visible = true;
830 shutdownFadeOut.start();
835 target: SessionBroadcast
836 onShowHome: if (shell.mode !== "greeter") showHome()
841 objectName: "urlDispatcher"
842 active: shell.mode === "greeter"
843 onUrlRequested: shell.activateURL(url)
850 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
853 ignoreUnknownSignals: true
854 onItemSnapshotRequested: itemGrabber.capture(item)
859 id: cursorHidingTimer
861 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
862 onTriggered: cursor.opacity = 0;
870 topBoundaryOffset: panel.panelHeight
871 enabled: shell.hasMouse && screenWindow.active
874 property bool mouseNeverMoved: true
876 target: cursor; property: "x"; value: shell.width / 2
877 when: cursor.mouseNeverMoved && cursor.visible
880 target: cursor; property: "y"; value: shell.height / 2
881 when: cursor.mouseNeverMoved && cursor.visible
884 confiningItem: stage.itemConfiningMouseCursor
888 readonly property var previewRectangle: stage.previewRectangle.target &&
889 stage.previewRectangle.target.dragging ?
890 stage.previewRectangle : null
892 onPushedLeftBoundary: {
893 if (buttons === Qt.NoButton) {
894 launcher.pushEdge(amount);
895 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
896 previewRectangle.maximizeLeft(amount);
900 onPushedRightBoundary: {
901 if (buttons === Qt.NoButton) {
902 rightEdgeBarrier.push(amount);
903 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
904 previewRectangle.maximizeRight(amount);
908 onPushedTopBoundary: {
909 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
910 previewRectangle.maximize(amount);
913 onPushedTopLeftCorner: {
914 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915 previewRectangle.maximizeTopLeft(amount);
918 onPushedTopRightCorner: {
919 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920 previewRectangle.maximizeTopRight(amount);
923 onPushedBottomLeftCorner: {
924 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
925 previewRectangle.maximizeBottomLeft(amount);
928 onPushedBottomRightCorner: {
929 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
930 previewRectangle.maximizeBottomRight(amount);
934 if (previewRectangle) {
935 previewRectangle.stop();
940 mouseNeverMoved = false;
944 Behavior on opacity { LomiriNumberAnimation {} }
947 // non-visual objects
949 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
954 id: shutdownFadeOutRectangle
961 NumberAnimation on opacity {
966 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
967 DBusLomiriSessionService.shutdown();