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/>.
20import QtQuick.Window 2.2
21import AccountsService 0.1
22import QtMir.Application 0.1
23import Lomiri.Components 1.3
24import Lomiri.Components.Popups 1.3
25import Lomiri.Gestures 0.1
26import Lomiri.Telephony 0.1 as Telephony
27import Lomiri.ModemConnectivity 0.1
28import Lomiri.Launcher 0.1
29import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
33import SessionBroadcast 0.1
42import "Components/PanelState"
43import Lomiri.Notifications 1.0 as NotificationBackend
44import Lomiri.Session 0.1
45import Lomiri.Indicators 0.1 as Indicators
47import WindowManager 1.0
53 readonly property bool lightMode: settings.lightMode
54 theme.name: lightMode ? "Lomiri.Components.Themes.Ambiance" :
55 "Lomiri.Components.Themes.SuruDark"
57 // to be set from outside
58 property int orientationAngle: 0
59 property int orientation
60 property Orientations orientations
61 property real nativeWidth
62 property real nativeHeight
63 property alias panelAreaShowProgress: panel.panelAreaShowProgress
64 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
65 property string mode: "full-greeter"
66 property alias oskEnabled: inputMethod.enabled
67 function updateFocusedAppOrientation() {
68 stage.updateFocusedAppOrientation();
70 function updateFocusedAppOrientationAnimated() {
71 stage.updateFocusedAppOrientationAnimated();
73 property bool hasMouse: false
74 property bool hasKeyboard: false
75 property bool hasTouchscreen: false
76 property bool supportsMultiColorLed: true
78 // The largest dimension, in pixels, of all of the screens this Shell is
80 // If a script sets the shell to 240x320 when it was 320x240, we could
81 // end up in a situation where our dimensions are 240x240 for a short time.
82 // Notifying the Wallpaper of both events would make it reload the image
83 // twice. So, we use a Binding { delayed: true }.
84 property real largestScreenDimension
87 restoreMode: Binding.RestoreBinding
89 property: "largestScreenDimension"
90 value: Math.max(nativeWidth, nativeHeight)
94 property alias lightIndicators: indicatorsModel.light
96 // to be read from outside
97 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
99 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
100 && stage.orientationChangesEnabled
101 && (!greeter.animating)
103 readonly property bool showingGreeter: greeter && greeter.shown
105 property bool startingUp: true
106 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
108 property int supportedOrientations: {
110 // Ensure we don't rotate during start up
111 return Qt.PrimaryOrientation;
112 } else if (notifications.topmostIsFullscreen) {
113 return Qt.PrimaryOrientation;
115 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
119 readonly property var mainApp: stage.mainApp
121 readonly property var topLevelSurfaceList: {
122 if (!WMScreen.currentWorkspace) return null;
123 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
127 _onMainAppChanged((mainApp ? mainApp.appId : ""));
130 target: ApplicationManager
131 function onFocusRequested(appId) {
132 if (shell.mainApp && shell.mainApp.appId === appId) {
133 _onMainAppChanged(appId);
138 // Calls attention back to the most important thing that's been focused
139 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
140 // goes over everything if it is locked)
141 // Must be called whenever app focus changes occur, even if the focus change
142 // is "nothing is focused". In that case, call with appId = ""
143 function _onMainAppChanged(appId) {
147 // If this happens on first boot, we may be in the
148 // wizard while receiving a call. A call is more
149 // important than the wizard so just bail out of it.
153 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
154 // If we are in the middle of a call, make dialer lockedApp. The
155 // Greeter will show it when it's notified of the focus.
156 // This can happen if user backs out of dialer back to greeter, then
157 // launches dialer again.
158 greeter.lockedApp = appId;
161 panel.indicators.hide();
162 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
165 // *Always* make sure the greeter knows that the focused app changed
166 if (greeter) greeter.notifyAppFocusRequested(appId);
169 // For autopilot consumption
170 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
172 // Note when greeter is waiting on PAM, so that we can disable edges until
173 // we know which user data to show and whether the session is locked.
174 readonly property bool waitingOnGreeter: greeter && greeter.waiting
176 // True when the user is logged in with no apps running
177 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
179 onAtDesktopChanged: {
180 if (atDesktop && stage && !stage.workspaceEnabled) {
185 property real edgeSize: units.gu(settings.edgeDragWidth)
188 id: wallpaperResolver
189 objectName: "wallpaperResolver"
191 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
192 readonly property bool hasCustomBackground: background != defaultBackground
195 id: backgroundSettings
196 schema.id: ((shell.showingGreeter == true) || (shell.mode === "full-greeter") || (shell.mode === "greeter")) ? "com.lomiri.Shell.Greeter" : "com.lomiri.Shell"
200 AccountsService.backgroundFile,
201 backgroundSettings.backgroundPictureUri,
206 readonly property alias greeter: greeterLoader.item
208 function activateApplication(appId) {
209 topLevelSurfaceList.pendingActivation();
211 // Either open the app in our own session, or -- if we're acting as a
212 // greeter -- ask the user's session to open it for us.
213 if (shell.mode === "greeter") {
214 activateURL("application:///" + appId + ".desktop");
221 function activateURL(url) {
222 SessionBroadcast.requestUrlStart(AccountsService.user, url);
223 greeter.notifyUserRequestedApp();
224 panel.indicators.hide();
227 function startApp(appId) {
228 if (!ApplicationManager.findApplication(appId)) {
229 ApplicationManager.startApplication(appId);
231 ApplicationManager.requestFocusApplication(appId);
234 function startLockedApp(app) {
235 topLevelSurfaceList.pendingActivation();
237 if (greeter.locked) {
238 greeter.lockedApp = app;
240 startApp(app); // locked apps are always in our same session
244 target: LauncherModel
245 restoreMode: Binding.RestoreBinding
246 property: "applicationManager"
247 value: ApplicationManager
250 Component.onCompleted: {
251 finishStartUpTimer.start();
259 id: physicalKeysMapper
260 objectName: "physicalKeysMapper"
262 onPowerKeyLongPressed: dialogs.showPowerDialog();
263 onVolumeDownTriggered: volumeControl.volumeDown();
264 onVolumeUpTriggered: volumeControl.volumeUp();
265 onScreenshotTriggered: itemGrabber.capture(shell);
269 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
274 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
275 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
279 objectName: "windowInputMonitor"
280 onHomeKeyActivated: {
281 // Ignore when greeter is active, to avoid pocket presses
282 if (!greeter.active) {
283 launcher.toggleDrawer(/* focusInputField */ false,
284 /* onlyOpen */ false,
285 /* alsoToggleLauncher */ true);
288 onTouchBegun: { cursor.opacity = 0; }
290 // move the (hidden) cursor to the last known touch position
291 var mappedCoords = mapFromItem(null, pos.x, pos.y);
292 cursor.x = mappedCoords.x;
293 cursor.y = mappedCoords.y;
294 cursor.mouseNeverMoved = false;
298 AvailableDesktopArea {
299 id: availableDesktopAreaItem
301 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
302 anchors.leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
307 schema.id: "com.lomiri.Shell"
312 objectName: "panelState"
319 height: parent.height
326 lightMode: shell.lightMode
328 dragAreaWidth: shell.edgeSize
329 background: wallpaperResolver.background
330 backgroundSourceSize: shell.largestScreenDimension
332 applicationManager: ApplicationManager
333 topLevelSurfaceList: shell.topLevelSurfaceList
334 inputMethodRect: inputMethod.visibleRect
335 rightEdgePushProgress: rightEdgeBarrier.progress
336 availableDesktopArea: availableDesktopAreaItem
337 launcherLeftMargin: launcher.visibleWidth
338 launcherLockedVisible: launcher.lockedVisible
339 topPanelHeight: panel.panelHeight
341 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
343 : shell.usageScenario
345 mode: usageScenario == "phone" ? "staged"
346 : usageScenario == "tablet" ? "stagedWithSideStage"
349 shellOrientation: shell.orientation
350 shellOrientationAngle: shell.orientationAngle
351 orientations: shell.orientations
352 nativeWidth: shell.nativeWidth
353 nativeHeight: shell.nativeHeight
355 allowInteractivity: (!greeter || !greeter.shown)
356 && panel.indicators.fullyClosed
357 && !notifications.useModal
358 && !launcher.takesFocus
360 suspended: greeter.shown
361 altTabPressed: physicalKeysMapper.altTabPressed
362 oskEnabled: shell.oskEnabled
363 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
364 panelState: panelState
366 onSpreadShownChanged: {
367 panel.indicators.hide();
368 panel.applicationMenus.hide();
375 minimumTouchPoints: 4
376 maximumTouchPoints: minimumTouchPoints
378 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
379 touchPoints.length >= minimumTouchPoints &&
380 touchPoints.length <= maximumTouchPoints
381 property bool wasPressed: false
383 onRecognisedPressChanged: {
384 if (recognisedPress) {
390 if (status !== TouchGestureArea.Recognized) {
391 if (status === TouchGestureArea.WaitingForTouch) {
392 if (wasPressed && !dragging) {
393 launcher.toggleDrawer(true);
404 objectName: "inputMethod"
407 topMargin: panel.panelHeight
408 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
410 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
415 objectName: "greeterLoader"
418 if (shell.mode != "shell") {
419 if (screenWindow.primary) return integratedGreeter;
420 return secondaryGreeter;
422 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
425 item.objectName = "greeter"
427 property bool toggleDrawerAfterUnlock: false
430 function onActiveChanged() {
434 // Show drawer in case showHome() requests it
435 if (greeterLoader.toggleDrawerAfterUnlock) {
436 launcher.toggleDrawer(false);
437 greeterLoader.toggleDrawerAfterUnlock = false;
446 id: integratedGreeter
449 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
450 hides: [launcher, panel.indicators, panel.applicationMenus]
451 tabletMode: shell.usageScenario != "phone"
452 usageMode: shell.usageScenario
453 orientation: shell.orientation
454 forcedUnlock: wizard.active || shell.mode === "full-shell"
455 background: wallpaperResolver.background
456 backgroundSourceSize: shell.largestScreenDimension
457 hasCustomBackground: wallpaperResolver.hasCustomBackground
458 inputMethodRect: inputMethod.visibleRect
459 hasKeyboard: shell.hasKeyboard
460 allowFingerprint: !dialogs.hasActiveDialog &&
461 !notifications.topmostIsFullscreen &&
462 !panel.indicators.shown
463 panelHeight: panel.panelHeight
465 // avoid overlapping with Launcher's edge drag area
466 // FIXME: Fix TouchRegistry & friends and remove this workaround
467 // Issue involves launcher's DDA getting disabled on a long
469 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
472 if (!tutorial.running) {
477 onEmergencyCall: startLockedApp("dialer-app")
484 hides: [launcher, panel.indicators]
489 // See powerConnection for why this is useful
490 id: showGreeterDelayed
493 // Go through the dbus service, because it has checks for whether
494 // we are even allowed to lock or not.
495 DBusLomiriSessionService.PromptLock();
503 function onHasCallsChanged() {
504 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
505 // We just received an incoming call while locked. The
506 // indicator will have already launched dialer-app for us, but
507 // there is a race between "hasCalls" changing and the dialer
508 // starting up. So in case we lose that race, we'll start/
509 // focus the dialer ourselves here too. Even if the indicator
510 // didn't launch the dialer for some reason (or maybe a call
511 // started via some other means), if an active call is
512 // happening, we want to be in the dialer.
513 startLockedApp("dialer-app")
522 function onStatusChanged(reason) {
523 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
524 !callManager.hasCalls && !wizard.active) {
525 // We don't want to simply call greeter.showNow() here, because
526 // that will take too long. Qt will delay button event
527 // handling until the greeter is done loading and may think the
528 // user held down the power button the whole time, leading to a
529 // power dialog being shown. Instead, delay showing the
530 // greeter until we've finished handling the event. We could
531 // make the greeter load asynchronously instead, but that
532 // introduces a whole host of timing issues, especially with
533 // its animations. So this is simpler.
534 showGreeterDelayed.start();
539 function showHome() {
540 greeter.notifyUserRequestedApp();
542 if (shell.mode === "greeter") {
543 SessionBroadcast.requestHomeShown(AccountsService.user);
545 if (!greeter.active) {
546 launcher.toggleDrawer(false);
548 greeterLoader.toggleDrawerAfterUnlock = true;
560 objectName: "fullscreenSwipeDown"
561 enabled: panel.state === "offscreen"
562 direction: SwipeArea.Downwards
563 immediateRecognition: false
572 panel.temporarilyShow()
580 anchors.fill: parent //because this draws indicator menus
581 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
582 lightMode: shell.lightMode
584 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
585 minimizedPanelHeight: units.gu(3)
586 expandedPanelHeight: units.gu(7)
587 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
591 available: tutorial.panelEnabled
592 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
593 && (!greeter || !greeter.hasLockedApp)
594 && !shell.waitingOnGreeter
595 && settings.enableIndicatorMenu
597 model: Indicators.IndicatorsModel {
599 // tablet and phone both use the same profile
600 // FIXME: use just "phone" for greeter too, but first fix
601 // greeter app launching to either load the app inside the
602 // greeter or tell the session to load the app. This will
603 // involve taking the url-dispatcher dbus name and using
604 // SessionBroadcast to tell the session.
605 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
606 Component.onCompleted: {
614 available: (!greeter || !greeter.shown)
615 && !shell.waitingOnGreeter
616 && !stage.spreadShown
619 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
620 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
622 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
623 || greeter.hasLockedApp
624 greeterShown: greeter && greeter.shown
625 hasKeyboard: shell.hasKeyboard
626 panelState: panelState
627 supportsMultiColorLed: shell.supportsMultiColorLed
632 objectName: "launcher"
634 anchors.top: parent.top
635 anchors.topMargin: inverted ? 0 : panel.panelHeight
636 anchors.bottom: parent.bottom
638 dragAreaWidth: shell.edgeSize
639 available: tutorial.launcherEnabled
640 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
641 && !greeter.hasLockedApp
642 && !shell.waitingOnGreeter
643 && shell.mode !== "greeter"
644 visible: shell.mode !== "greeter"
645 inverted: shell.usageScenario !== "desktop"
646 superPressed: physicalKeysMapper.superPressed
647 superTabPressed: physicalKeysMapper.superTabPressed
648 panelWidth: units.gu(settings.launcherWidth)
649 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
650 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
651 topPanelHeight: panel.panelHeight
652 lightMode: shell.lightMode
653 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
654 privateMode: greeter.active
655 background: wallpaperResolver.background
657 // It can be assumed that the Launcher and Panel would overlap if
658 // the Panel is open and taking up the full width of the shell
659 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
661 // The "autohideLauncher" setting is only valid in desktop mode
662 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
664 // The Launcher should absolutely not be locked visible under some
666 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
668 onShowDashHome: showHome()
669 onLauncherApplicationSelected: {
670 greeter.notifyUserRequestedApp();
671 shell.activateApplication(appId);
675 panel.indicators.hide();
676 panel.applicationMenus.hide();
679 onDrawerShownChanged: {
681 panel.indicators.hide();
682 panel.applicationMenus.hide();
692 shortcut: Qt.MetaModifier | Qt.Key_A
694 launcher.toggleDrawer(true);
698 shortcut: Qt.AltModifier | Qt.Key_F1
700 launcher.openForKeyboardNavigation();
704 shortcut: Qt.MetaModifier | Qt.Key_0
706 if (LauncherModel.get(9)) {
707 activateApplication(LauncherModel.get(9).appId);
714 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
716 if (LauncherModel.get(index)) {
717 activateApplication(LauncherModel.get(index).appId);
724 KeyboardShortcutsOverlay {
725 objectName: "shortcutsOverlay"
726 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
727 && height < parent.height - padding - panel.panelHeight
728 anchors.centerIn: parent
729 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
730 anchors.verticalCenterOffset: panel.panelHeight/2
732 opacity: enabled ? 0.95 : 0
734 Behavior on opacity {
735 LomiriNumberAnimation {}
741 objectName: "tutorial"
744 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
745 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
746 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
747 inputMethod.visible ||
748 (launcher.shown && !launcher.lockedVisible) ||
749 panel.indicators.shown || stage.rightEdgeDragProgress > 0
750 usageScenario: shell.usageScenario
751 lastInputTimestamp: inputFilter.lastInputTimestamp
761 deferred: shell.mode === "greeter"
763 function unlockWhenDoneWithWizard() {
765 ModemConnectivity.unlockAllModems();
769 Component.onCompleted: unlockWhenDoneWithWizard()
770 onActiveChanged: unlockWhenDoneWithWizard()
773 MouseArea { // modal notifications prevent interacting with other contents
775 visible: notifications.useModal
782 model: NotificationBackend.Model
784 hasMouse: shell.hasMouse
785 background: wallpaperResolver.background
786 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
788 y: topmostIsFullscreen ? 0 : panel.panelHeight
789 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
794 when: overlay.width <= units.gu(60)
796 target: notifications
797 anchors.left: parent.left
798 anchors.right: parent.right
803 when: overlay.width > units.gu(60)
805 target: notifications
806 anchors.left: undefined
807 anchors.right: parent.right
809 PropertyChanges { target: notifications; width: units.gu(38) }
816 enabled: !greeter.shown
818 // NB: it does its own positioning according to the specified edge
822 panel.indicators.hide()
825 material: Component {
831 anchors.centerIn: parent
833 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
834 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
844 objectName: "dialogs"
846 visible: hasActiveDialog
848 usageScenario: shell.usageScenario
849 hasKeyboard: shell.hasKeyboard
851 shutdownFadeOutRectangle.enabled = true;
852 shutdownFadeOutRectangle.visible = true;
853 shutdownFadeOut.start();
858 target: SessionBroadcast
859 function onShowHome() { if (shell.mode !== "greeter") showHome() }
864 objectName: "urlDispatcher"
865 active: shell.mode === "greeter"
866 onUrlRequested: shell.activateURL(url)
873 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
876 ignoreUnknownSignals: true
877 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
882 id: cursorHidingTimer
884 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
885 onTriggered: cursor.opacity = 0;
893 topBoundaryOffset: panel.panelHeight
894 active: shell.hasMouse
897 property bool mouseNeverMoved: true
899 target: cursor; property: "x"; value: shell.width / 2
900 restoreMode: Binding.RestoreBinding
901 when: cursor.mouseNeverMoved && cursor.visible
904 target: cursor; property: "y"; value: shell.height / 2
905 restoreMode: Binding.RestoreBinding
906 when: cursor.mouseNeverMoved && cursor.visible
909 confiningItem: stage.itemConfiningMouseCursor
913 readonly property var previewRectangle: stage.previewRectangle.target &&
914 stage.previewRectangle.target.dragging ?
915 stage.previewRectangle : null
917 onPushedLeftBoundary: {
918 if (buttons === Qt.NoButton) {
919 launcher.pushEdge(amount);
920 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
921 previewRectangle.maximizeLeft(amount);
925 onPushedRightBoundary: {
926 if (buttons === Qt.NoButton) {
927 rightEdgeBarrier.push(amount);
928 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
929 previewRectangle.maximizeRight(amount);
933 onPushedTopBoundary: {
934 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
935 previewRectangle.maximize(amount);
938 onPushedTopLeftCorner: {
939 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
940 previewRectangle.maximizeTopLeft(amount);
943 onPushedTopRightCorner: {
944 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
945 previewRectangle.maximizeTopRight(amount);
948 onPushedBottomLeftCorner: {
949 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
950 previewRectangle.maximizeBottomLeft(amount);
953 onPushedBottomRightCorner: {
954 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
955 previewRectangle.maximizeBottomRight(amount);
959 if (previewRectangle) {
960 previewRectangle.stop();
965 mouseNeverMoved = false;
969 Behavior on opacity { LomiriNumberAnimation {} }
972 // non-visual objects
974 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
979 id: shutdownFadeOutRectangle
986 NumberAnimation on opacity {
991 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
992 DBusLomiriSessionService.shutdown();