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 === "lomiri-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
339 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
341 : shell.usageScenario
343 mode: usageScenario == "phone" ? "staged"
344 : usageScenario == "tablet" ? "stagedWithSideStage"
347 shellOrientation: shell.orientation
348 shellOrientationAngle: shell.orientationAngle
349 orientations: shell.orientations
350 nativeWidth: shell.nativeWidth
351 nativeHeight: shell.nativeHeight
353 allowInteractivity: (!greeter || !greeter.shown)
354 && panel.indicators.fullyClosed
355 && !notifications.useModal
356 && !launcher.takesFocus
358 suspended: greeter.shown
359 altTabPressed: physicalKeysMapper.altTabPressed
360 oskEnabled: shell.oskEnabled
361 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
362 panelState: panelState
364 onSpreadShownChanged: {
365 panel.indicators.hide();
366 panel.applicationMenus.hide();
373 minimumTouchPoints: 4
374 maximumTouchPoints: minimumTouchPoints
376 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
377 touchPoints.length >= minimumTouchPoints &&
378 touchPoints.length <= maximumTouchPoints
379 property bool wasPressed: false
381 onRecognisedPressChanged: {
382 if (recognisedPress) {
388 if (status !== TouchGestureArea.Recognized) {
389 if (status === TouchGestureArea.WaitingForTouch) {
390 if (wasPressed && !dragging) {
391 launcher.toggleDrawer(true);
402 objectName: "inputMethod"
405 topMargin: panel.panelHeight
406 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
408 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
413 objectName: "greeterLoader"
416 if (shell.mode != "shell") {
417 if (screenWindow.primary) return integratedGreeter;
418 return secondaryGreeter;
420 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
423 item.objectName = "greeter"
425 property bool toggleDrawerAfterUnlock: false
428 function onActiveChanged() {
432 // Show drawer in case showHome() requests it
433 if (greeterLoader.toggleDrawerAfterUnlock) {
434 launcher.toggleDrawer(false);
435 greeterLoader.toggleDrawerAfterUnlock = false;
444 id: integratedGreeter
447 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
448 hides: [launcher, panel.indicators, panel.applicationMenus]
449 tabletMode: shell.usageScenario != "phone"
450 usageMode: shell.usageScenario
451 orientation: shell.orientation
452 forcedUnlock: wizard.active || shell.mode === "full-shell"
453 background: wallpaperResolver.background
454 backgroundSourceSize: shell.largestScreenDimension
455 hasCustomBackground: wallpaperResolver.hasCustomBackground
456 inputMethodRect: inputMethod.visibleRect
457 hasKeyboard: shell.hasKeyboard
458 allowFingerprint: !dialogs.hasActiveDialog &&
459 !notifications.topmostIsFullscreen &&
460 !panel.indicators.shown
461 panelHeight: panel.panelHeight
463 // avoid overlapping with Launcher's edge drag area
464 // FIXME: Fix TouchRegistry & friends and remove this workaround
465 // Issue involves launcher's DDA getting disabled on a long
467 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
470 if (!tutorial.running) {
475 onEmergencyCall: startLockedApp("lomiri-dialer-app")
482 hides: [launcher, panel.indicators]
487 // See powerConnection for why this is useful
488 id: showGreeterDelayed
491 // Go through the dbus service, because it has checks for whether
492 // we are even allowed to lock or not.
493 DBusLomiriSessionService.PromptLock();
501 function onHasCallsChanged() {
502 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "lomiri-dialer-app") {
503 // We just received an incoming call while locked. The
504 // indicator will have already launched lomiri-dialer-app for
505 // us, but there is a race between "hasCalls" changing and the
506 // dialer starting up. So in case we lose that race, we'll
507 // start/focus the dialer ourselves here too. Even if the
508 // indicator didn't launch the dialer for some reason (or maybe
509 // a call started via some other means), if an active call is
510 // happening, we want to be in the dialer.
511 startLockedApp("lomiri-dialer-app")
520 function onStatusChanged(reason) {
521 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
522 !callManager.hasCalls && !wizard.active) {
523 // We don't want to simply call greeter.showNow() here, because
524 // that will take too long. Qt will delay button event
525 // handling until the greeter is done loading and may think the
526 // user held down the power button the whole time, leading to a
527 // power dialog being shown. Instead, delay showing the
528 // greeter until we've finished handling the event. We could
529 // make the greeter load asynchronously instead, but that
530 // introduces a whole host of timing issues, especially with
531 // its animations. So this is simpler.
532 showGreeterDelayed.start();
537 function showHome() {
538 greeter.notifyUserRequestedApp();
540 if (shell.mode === "greeter") {
541 SessionBroadcast.requestHomeShown(AccountsService.user);
543 if (!greeter.active) {
544 launcher.toggleDrawer(false);
546 greeterLoader.toggleDrawerAfterUnlock = true;
558 objectName: "fullscreenSwipeDown"
559 enabled: panel.state === "offscreen"
560 direction: SwipeArea.Downwards
561 immediateRecognition: false
570 panel.temporarilyShow()
578 anchors.fill: parent //because this draws indicator menus
579 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
580 lightMode: shell.lightMode
582 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
583 minimizedPanelHeight: units.gu(3)
584 expandedPanelHeight: units.gu(7)
585 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
589 available: tutorial.panelEnabled
590 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
591 && (!greeter || !greeter.hasLockedApp)
592 && !shell.waitingOnGreeter
593 && settings.enableIndicatorMenu
595 model: Indicators.IndicatorsModel {
597 // tablet and phone both use the same profile
598 // FIXME: use just "phone" for greeter too, but first fix
599 // greeter app launching to either load the app inside the
600 // greeter or tell the session to load the app. This will
601 // involve taking the url-dispatcher dbus name and using
602 // SessionBroadcast to tell the session.
603 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
604 Component.onCompleted: {
612 available: (!greeter || !greeter.shown)
613 && !shell.waitingOnGreeter
614 && !stage.spreadShown
617 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
618 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
620 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
621 || greeter.hasLockedApp
622 greeterShown: greeter && greeter.shown
623 hasKeyboard: shell.hasKeyboard
624 panelState: panelState
625 supportsMultiColorLed: shell.supportsMultiColorLed
630 objectName: "launcher"
632 anchors.top: parent.top
633 anchors.topMargin: inverted ? 0 : panel.panelHeight
634 anchors.bottom: parent.bottom
636 dragAreaWidth: shell.edgeSize
637 available: tutorial.launcherEnabled
638 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
639 && !greeter.hasLockedApp
640 && !shell.waitingOnGreeter
641 && shell.mode !== "greeter"
642 visible: shell.mode !== "greeter"
643 inverted: shell.usageScenario !== "desktop"
644 superPressed: physicalKeysMapper.superPressed
645 superTabPressed: physicalKeysMapper.superTabPressed
646 panelWidth: units.gu(settings.launcherWidth)
647 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
648 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
649 topPanelHeight: panel.panelHeight
650 lightMode: shell.lightMode
651 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
652 privateMode: greeter.active
653 background: wallpaperResolver.background
655 // It can be assumed that the Launcher and Panel would overlap if
656 // the Panel is open and taking up the full width of the shell
657 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
659 // The "autohideLauncher" setting is only valid in desktop mode
660 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
662 // The Launcher should absolutely not be locked visible under some
664 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
666 onShowDashHome: showHome()
667 onLauncherApplicationSelected: {
668 greeter.notifyUserRequestedApp();
669 shell.activateApplication(appId);
673 panel.indicators.hide();
674 panel.applicationMenus.hide();
677 onDrawerShownChanged: {
679 panel.indicators.hide();
680 panel.applicationMenus.hide();
690 shortcut: Qt.MetaModifier | Qt.Key_A
692 launcher.toggleDrawer(true);
696 shortcut: Qt.AltModifier | Qt.Key_F1
698 launcher.openForKeyboardNavigation();
702 shortcut: Qt.MetaModifier | Qt.Key_0
704 if (LauncherModel.get(9)) {
705 activateApplication(LauncherModel.get(9).appId);
712 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
714 if (LauncherModel.get(index)) {
715 activateApplication(LauncherModel.get(index).appId);
722 KeyboardShortcutsOverlay {
723 objectName: "shortcutsOverlay"
724 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
725 && height < parent.height - padding - panel.panelHeight
726 anchors.centerIn: parent
727 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
728 anchors.verticalCenterOffset: panel.panelHeight/2
730 opacity: enabled ? 0.95 : 0
732 Behavior on opacity {
733 LomiriNumberAnimation {}
739 objectName: "tutorial"
742 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
743 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
744 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
745 inputMethod.visible ||
746 (launcher.shown && !launcher.lockedVisible) ||
747 panel.indicators.shown || stage.rightEdgeDragProgress > 0
748 usageScenario: shell.usageScenario
749 lastInputTimestamp: inputFilter.lastInputTimestamp
759 deferred: shell.mode === "greeter"
761 function unlockWhenDoneWithWizard() {
763 ModemConnectivity.unlockAllModems();
767 Component.onCompleted: unlockWhenDoneWithWizard()
768 onActiveChanged: unlockWhenDoneWithWizard()
771 MouseArea { // modal notifications prevent interacting with other contents
773 visible: notifications.useModal
780 model: NotificationBackend.Model
782 hasMouse: shell.hasMouse
783 background: wallpaperResolver.background
784 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
786 y: topmostIsFullscreen ? 0 : panel.panelHeight
787 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
792 when: overlay.width <= units.gu(60)
794 target: notifications
795 anchors.left: parent.left
796 anchors.right: parent.right
801 when: overlay.width > units.gu(60)
803 target: notifications
804 anchors.left: undefined
805 anchors.right: parent.right
807 PropertyChanges { target: notifications; width: units.gu(38) }
814 enabled: !greeter.shown
816 // NB: it does its own positioning according to the specified edge
820 panel.indicators.hide()
823 material: Component {
829 anchors.centerIn: parent
831 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
832 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
842 objectName: "dialogs"
844 visible: hasActiveDialog
846 usageScenario: shell.usageScenario
847 hasKeyboard: shell.hasKeyboard
849 shutdownFadeOutRectangle.enabled = true;
850 shutdownFadeOutRectangle.visible = true;
851 shutdownFadeOut.start();
856 target: SessionBroadcast
857 function onShowHome() { if (shell.mode !== "greeter") showHome() }
862 objectName: "urlDispatcher"
863 active: shell.mode === "greeter"
864 onUrlRequested: shell.activateURL(url)
871 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
874 ignoreUnknownSignals: true
875 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
880 id: cursorHidingTimer
882 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
883 onTriggered: cursor.opacity = 0;
891 topBoundaryOffset: panel.panelHeight
892 active: shell.hasMouse
895 property bool mouseNeverMoved: true
897 target: cursor; property: "x"; value: shell.width / 2
898 restoreMode: Binding.RestoreBinding
899 when: cursor.mouseNeverMoved && cursor.visible
902 target: cursor; property: "y"; value: shell.height / 2
903 restoreMode: Binding.RestoreBinding
904 when: cursor.mouseNeverMoved && cursor.visible
907 confiningItem: stage.itemConfiningMouseCursor
911 readonly property var previewRectangle: stage.previewRectangle.target &&
912 stage.previewRectangle.target.dragging ?
913 stage.previewRectangle : null
915 onPushedLeftBoundary: {
916 if (buttons === Qt.NoButton) {
917 launcher.pushEdge(amount);
918 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
919 previewRectangle.maximizeLeft(amount);
923 onPushedRightBoundary: {
924 if (buttons === Qt.NoButton) {
925 rightEdgeBarrier.push(amount);
926 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
927 previewRectangle.maximizeRight(amount);
931 onPushedTopBoundary: {
932 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
933 previewRectangle.maximize(amount);
936 onPushedTopLeftCorner: {
937 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
938 previewRectangle.maximizeTopLeft(amount);
941 onPushedTopRightCorner: {
942 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
943 previewRectangle.maximizeTopRight(amount);
946 onPushedBottomLeftCorner: {
947 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
948 previewRectangle.maximizeBottomLeft(amount);
951 onPushedBottomRightCorner: {
952 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
953 previewRectangle.maximizeBottomRight(amount);
957 if (previewRectangle) {
958 previewRectangle.stop();
963 mouseNeverMoved = false;
967 Behavior on opacity { LomiriNumberAnimation {} }
970 // non-visual objects
972 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
977 id: shutdownFadeOutRectangle
984 NumberAnimation on opacity {
989 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
990 DBusLomiriSessionService.shutdown();