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: resolvedImage != 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.resolvedImage
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.resolvedImage
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")
477 // Quit the greeter as soon as a session has been started
479 if (shell.mode == "greeter")
488 hides: [launcher, panel.indicators]
493 // See powerConnection for why this is useful
494 id: showGreeterDelayed
497 // Go through the dbus service, because it has checks for whether
498 // we are even allowed to lock or not.
499 DBusLomiriSessionService.PromptLock();
507 function onHasCallsChanged() {
508 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "lomiri-dialer-app") {
509 // We just received an incoming call while locked. The
510 // indicator will have already launched lomiri-dialer-app for
511 // us, but there is a race between "hasCalls" changing and the
512 // dialer starting up. So in case we lose that race, we'll
513 // start/focus the dialer ourselves here too. Even if the
514 // indicator didn't launch the dialer for some reason (or maybe
515 // a call started via some other means), if an active call is
516 // happening, we want to be in the dialer.
517 startLockedApp("lomiri-dialer-app")
526 function onStatusChanged(reason) {
527 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
528 !callManager.hasCalls && !wizard.active) {
529 // We don't want to simply call greeter.showNow() here, because
530 // that will take too long. Qt will delay button event
531 // handling until the greeter is done loading and may think the
532 // user held down the power button the whole time, leading to a
533 // power dialog being shown. Instead, delay showing the
534 // greeter until we've finished handling the event. We could
535 // make the greeter load asynchronously instead, but that
536 // introduces a whole host of timing issues, especially with
537 // its animations. So this is simpler.
538 showGreeterDelayed.start();
543 function showHome() {
544 greeter.notifyUserRequestedApp();
546 if (shell.mode === "greeter") {
547 SessionBroadcast.requestHomeShown(AccountsService.user);
549 if (!greeter.active) {
550 launcher.toggleDrawer(false);
552 greeterLoader.toggleDrawerAfterUnlock = true;
564 objectName: "fullscreenSwipeDown"
565 enabled: panel.state === "offscreen"
566 direction: SwipeArea.Downwards
567 immediateRecognition: false
576 panel.temporarilyShow()
584 anchors.fill: parent //because this draws indicator menus
585 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
586 lightMode: shell.lightMode
588 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
589 minimizedPanelHeight: units.gu(3)
590 expandedPanelHeight: units.gu(7)
591 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
595 available: tutorial.panelEnabled
596 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
597 && (!greeter || !greeter.hasLockedApp)
598 && !shell.waitingOnGreeter
599 && settings.enableIndicatorMenu
601 model: Indicators.IndicatorsModel {
603 // tablet and phone both use the same profile
604 // FIXME: use just "phone" for greeter too, but first fix
605 // greeter app launching to either load the app inside the
606 // greeter or tell the session to load the app. This will
607 // involve taking the url-dispatcher dbus name and using
608 // SessionBroadcast to tell the session.
609 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
610 Component.onCompleted: {
618 available: (!greeter || !greeter.shown)
619 && !shell.waitingOnGreeter
620 && !stage.spreadShown
623 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
624 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
626 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
627 || greeter.hasLockedApp
628 greeterShown: greeter && greeter.shown
629 hasKeyboard: shell.hasKeyboard
630 panelState: panelState
631 supportsMultiColorLed: shell.supportsMultiColorLed
636 objectName: "launcher"
638 anchors.top: parent.top
639 anchors.topMargin: inverted ? 0 : panel.panelHeight
640 anchors.bottom: parent.bottom
642 dragAreaWidth: shell.edgeSize
643 available: tutorial.launcherEnabled
644 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
645 && !greeter.hasLockedApp
646 && !shell.waitingOnGreeter
647 && shell.mode !== "greeter"
648 visible: shell.mode !== "greeter"
649 inverted: shell.usageScenario !== "desktop"
650 superPressed: physicalKeysMapper.superPressed
651 superTabPressed: physicalKeysMapper.superTabPressed
652 panelWidth: units.gu(settings.launcherWidth)
653 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
654 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
655 topPanelHeight: panel.panelHeight
656 lightMode: shell.lightMode
657 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
658 privateMode: greeter.active
659 background: wallpaperResolver.resolvedImage
661 // It can be assumed that the Launcher and Panel would overlap if
662 // the Panel is open and taking up the full width of the shell
663 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
665 // The "autohideLauncher" setting is only valid in desktop mode
666 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
668 // The Launcher should absolutely not be locked visible under some
670 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
672 onShowDashHome: showHome()
673 onLauncherApplicationSelected: {
674 greeter.notifyUserRequestedApp();
675 shell.activateApplication(appId);
679 panel.indicators.hide();
680 panel.applicationMenus.hide();
683 onDrawerShownChanged: {
685 panel.indicators.hide();
686 panel.applicationMenus.hide();
696 shortcut: Qt.MetaModifier | Qt.Key_A
698 launcher.toggleDrawer(true);
702 shortcut: Qt.AltModifier | Qt.Key_F1
704 launcher.openForKeyboardNavigation();
708 shortcut: Qt.MetaModifier | Qt.Key_0
710 if (LauncherModel.get(9)) {
711 activateApplication(LauncherModel.get(9).appId);
718 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
720 if (LauncherModel.get(index)) {
721 activateApplication(LauncherModel.get(index).appId);
728 KeyboardShortcutsOverlay {
729 objectName: "shortcutsOverlay"
730 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
731 && height < parent.height - padding - panel.panelHeight
732 anchors.centerIn: parent
733 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
734 anchors.verticalCenterOffset: panel.panelHeight/2
736 opacity: enabled ? 0.95 : 0
738 Behavior on opacity {
739 LomiriNumberAnimation {}
745 objectName: "tutorial"
748 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
749 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
750 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
751 inputMethod.visible ||
752 (launcher.shown && !launcher.lockedVisible) ||
753 panel.indicators.shown || stage.rightEdgeDragProgress > 0
754 usageScenario: shell.usageScenario
755 lastInputTimestamp: inputFilter.lastInputTimestamp
765 deferred: shell.mode === "greeter"
767 function unlockWhenDoneWithWizard() {
769 ModemConnectivity.unlockAllModems();
773 Component.onCompleted: unlockWhenDoneWithWizard()
774 onActiveChanged: unlockWhenDoneWithWizard()
777 MouseArea { // modal notifications prevent interacting with other contents
779 visible: notifications.useModal
786 model: NotificationBackend.Model
788 hasMouse: shell.hasMouse
789 background: wallpaperResolver.resolvedImage
790 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
792 y: topmostIsFullscreen ? 0 : panel.panelHeight
793 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
798 when: overlay.width <= units.gu(60)
800 target: notifications
801 anchors.left: parent.left
802 anchors.right: parent.right
807 when: overlay.width > units.gu(60)
809 target: notifications
810 anchors.left: undefined
811 anchors.right: parent.right
813 PropertyChanges { target: notifications; width: units.gu(38) }
820 enabled: !greeter.shown
822 // NB: it does its own positioning according to the specified edge
826 panel.indicators.hide()
829 material: Component {
835 anchors.centerIn: parent
837 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
838 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
848 objectName: "dialogs"
850 visible: hasActiveDialog
852 usageScenario: shell.usageScenario
853 hasKeyboard: shell.hasKeyboard
855 shutdownFadeOutRectangle.enabled = true;
856 shutdownFadeOutRectangle.visible = true;
857 shutdownFadeOut.start();
862 target: SessionBroadcast
863 function onShowHome() { if (shell.mode !== "greeter") showHome() }
868 objectName: "urlDispatcher"
869 active: shell.mode === "greeter"
870 onUrlRequested: shell.activateURL(url)
877 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
880 ignoreUnknownSignals: true
881 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
886 id: cursorHidingTimer
888 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
889 onTriggered: cursor.opacity = 0;
897 topBoundaryOffset: panel.panelHeight
898 enabled: shell.hasMouse && screenWindow.active
901 property bool mouseNeverMoved: true
903 target: cursor; property: "x"; value: shell.width / 2
904 restoreMode: Binding.RestoreBinding
905 when: cursor.mouseNeverMoved && cursor.visible
908 target: cursor; property: "y"; value: shell.height / 2
909 restoreMode: Binding.RestoreBinding
910 when: cursor.mouseNeverMoved && cursor.visible
913 confiningItem: stage.itemConfiningMouseCursor
917 readonly property var previewRectangle: stage.previewRectangle.target &&
918 stage.previewRectangle.target.dragging ?
919 stage.previewRectangle : null
921 onPushedLeftBoundary: {
922 if (buttons === Qt.NoButton) {
923 launcher.pushEdge(amount);
924 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
925 previewRectangle.maximizeLeft(amount);
929 onPushedRightBoundary: {
930 if (buttons === Qt.NoButton) {
931 rightEdgeBarrier.push(amount);
932 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
933 previewRectangle.maximizeRight(amount);
937 onPushedTopBoundary: {
938 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
939 previewRectangle.maximize(amount);
942 onPushedTopLeftCorner: {
943 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
944 previewRectangle.maximizeTopLeft(amount);
947 onPushedTopRightCorner: {
948 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
949 previewRectangle.maximizeTopRight(amount);
952 onPushedBottomLeftCorner: {
953 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
954 previewRectangle.maximizeBottomLeft(amount);
957 onPushedBottomRightCorner: {
958 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
959 previewRectangle.maximizeBottomRight(amount);
963 if (previewRectangle) {
964 previewRectangle.stop();
969 mouseNeverMoved = false;
973 Behavior on opacity { LomiriNumberAnimation {} }
976 // non-visual objects
978 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
983 id: shutdownFadeOutRectangle
990 NumberAnimation on opacity {
995 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
996 DBusLomiriSessionService.shutdown();