Lomiri
Loading...
Searching...
No Matches
Shell.qml
1/*
2 * Copyright (C) 2013-2016 Canonical Ltd.
3 * Copyright (C) 2019-2021 UBports Foundation
4 *
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.
8 *
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.
13 *
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/>.
16 */
17
18import QtQuick 2.12
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
29import GSettings 1.0
30import Utils 0.1
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stage"
39import "Tutorial"
40import "Wizard"
41import "Components/PanelState"
42import Lomiri.Notifications 1.0 as NotificationBackend
43import Lomiri.Session 0.1
44import Lomiri.Indicators 0.1 as Indicators
45import Cursor 1.1
46import WindowManager 1.0
47
48
49StyledItem {
50 id: shell
51
52 readonly property bool lightMode: settings.lightMode
53 theme.name: lightMode ? "Lomiri.Components.Themes.Ambiance" :
54 "Lomiri.Components.Themes.SuruDark"
55
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();
68 }
69 function updateFocusedAppOrientationAnimated() {
70 stage.updateFocusedAppOrientationAnimated();
71 }
72 property bool hasMouse: false
73 property bool hasKeyboard: false
74 property bool hasTouchscreen: false
75 property bool supportsMultiColorLed: true
76
77 // The largest dimension, in pixels, of all of the screens this Shell is
78 // operating on.
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
84 Binding {
85 target: shell
86 delayed: true
87 property: "largestScreenDimension"
88 value: Math.max(nativeWidth, nativeHeight)
89 }
90
91 // Used by tests
92 property alias lightIndicators: indicatorsModel.light
93
94 // to be read from outside
95 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
96
97 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
98 && stage.orientationChangesEnabled
99 && (!greeter.animating)
100
101 readonly property bool showingGreeter: greeter && greeter.shown
102
103 property bool startingUp: true
104 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
105
106 property int supportedOrientations: {
107 if (startingUp) {
108 // Ensure we don't rotate during start up
109 return Qt.PrimaryOrientation;
110 } else if (notifications.topmostIsFullscreen) {
111 return Qt.PrimaryOrientation;
112 } else {
113 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
114 }
115 }
116
117 readonly property var mainApp: stage.mainApp
118
119 readonly property var topLevelSurfaceList: {
120 if (!WMScreen.currentWorkspace) return null;
121 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
122 }
123
124 onMainAppChanged: {
125 _onMainAppChanged((mainApp ? mainApp.appId : ""));
126 }
127 Connections {
128 target: ApplicationManager
129 onFocusRequested: {
130 if (shell.mainApp && shell.mainApp.appId === appId) {
131 _onMainAppChanged(appId);
132 }
133 }
134 }
135
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) {
142
143 if (appId !== "") {
144 if (wizard.active) {
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.
148 wizard.hide();
149 }
150
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;
157 }
158
159 panel.indicators.hide();
160 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
161 }
162
163 // *Always* make sure the greeter knows that the focused app changed
164 if (greeter) greeter.notifyAppFocusRequested(appId);
165 }
166
167 // For autopilot consumption
168 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
169
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
173
174 // True when the user is logged in with no apps running
175 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
176
177 onAtDesktopChanged: {
178 if (atDesktop && stage) {
179 stage.closeSpread();
180 }
181 }
182
183 property real edgeSize: units.gu(settings.edgeDragWidth)
184
185 WallpaperResolver {
186 id: wallpaperResolver
187 objectName: "wallpaperResolver"
188
189 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
190 readonly property bool hasCustomBackground: background != defaultBackground
191
192 GSettings {
193 id: backgroundSettings
194 schema.id: ((shell.showingGreeter == true) || (shell.mode === "full-greeter") || (shell.mode === "greeter")) ? "com.lomiri.Shell.Greeter" : "com.lomiri.Shell"
195 }
196
197 candidates: [
198 AccountsService.backgroundFile,
199 backgroundSettings.backgroundPictureUri,
200 defaultBackground
201 ]
202 }
203
204 readonly property alias greeter: greeterLoader.item
205
206 function activateApplication(appId) {
207 topLevelSurfaceList.pendingActivation();
208
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");
213 } else {
214 startApp(appId);
215 }
216 stage.focus = true;
217 }
218
219 function activateURL(url) {
220 SessionBroadcast.requestUrlStart(AccountsService.user, url);
221 greeter.notifyUserRequestedApp();
222 panel.indicators.hide();
223 }
224
225 function startApp(appId) {
226 if (!ApplicationManager.findApplication(appId)) {
227 ApplicationManager.startApplication(appId);
228 }
229 ApplicationManager.requestFocusApplication(appId);
230 }
231
232 function startLockedApp(app) {
233 topLevelSurfaceList.pendingActivation();
234
235 if (greeter.locked) {
236 greeter.lockedApp = app;
237 }
238 startApp(app); // locked apps are always in our same session
239 }
240
241 Binding {
242 target: LauncherModel
243 property: "applicationManager"
244 value: ApplicationManager
245 }
246
247 Component.onCompleted: {
248 finishStartUpTimer.start();
249 }
250
251 VolumeControl {
252 id: volumeControl
253 }
254
255 PhysicalKeysMapper {
256 id: physicalKeysMapper
257 objectName: "physicalKeysMapper"
258
259 onPowerKeyLongPressed: dialogs.showPowerDialog();
260 onVolumeDownTriggered: volumeControl.volumeDown();
261 onVolumeUpTriggered: volumeControl.volumeUp();
262 onScreenshotTriggered: itemGrabber.capture(shell);
263 }
264
265 GlobalShortcut {
266 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
267 }
268
269 WindowInputFilter {
270 id: inputFilter
271 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
272 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
273 }
274
275 WindowInputMonitor {
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);
283 }
284 }
285 onTouchBegun: { cursor.opacity = 0; }
286 onTouchEnded: {
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;
292 }
293 }
294
295 AvailableDesktopArea {
296 id: availableDesktopAreaItem
297 anchors.fill: parent
298 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
299 anchors.leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
300 }
301
302 GSettings {
303 id: settings
304 schema.id: "com.lomiri.Shell"
305 }
306
307 PanelState {
308 id: panelState
309 objectName: "panelState"
310 }
311
312 Item {
313 id: stages
314 objectName: "stages"
315 width: parent.width
316 height: parent.height
317
318 Stage {
319 id: stage
320 objectName: "stage"
321 anchors.fill: parent
322 focus: true
323 lightMode: shell.lightMode
324
325 dragAreaWidth: shell.edgeSize
326 background: wallpaperResolver.background
327 backgroundSourceSize: shell.largestScreenDimension
328
329 applicationManager: ApplicationManager
330 topLevelSurfaceList: shell.topLevelSurfaceList
331 inputMethodRect: inputMethod.visibleRect
332 rightEdgePushProgress: rightEdgeBarrier.progress
333 availableDesktopArea: availableDesktopAreaItem
334 launcherLeftMargin: launcher.visibleWidth
335
336 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
337 ? "phone"
338 : shell.usageScenario
339
340 mode: usageScenario == "phone" ? "staged"
341 : usageScenario == "tablet" ? "stagedWithSideStage"
342 : "windowed"
343
344 shellOrientation: shell.orientation
345 shellOrientationAngle: shell.orientationAngle
346 orientations: shell.orientations
347 nativeWidth: shell.nativeWidth
348 nativeHeight: shell.nativeHeight
349
350 allowInteractivity: (!greeter || !greeter.shown)
351 && panel.indicators.fullyClosed
352 && !notifications.useModal
353 && !launcher.takesFocus
354
355 suspended: greeter.shown
356 altTabPressed: physicalKeysMapper.altTabPressed
357 oskEnabled: shell.oskEnabled
358 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
359 panelState: panelState
360
361 onSpreadShownChanged: {
362 panel.indicators.hide();
363 panel.applicationMenus.hide();
364 }
365 }
366
367 TouchGestureArea {
368 anchors.fill: stage
369
370 minimumTouchPoints: 4
371 maximumTouchPoints: minimumTouchPoints
372
373 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
374 touchPoints.length >= minimumTouchPoints &&
375 touchPoints.length <= maximumTouchPoints
376 property bool wasPressed: false
377
378 onRecognisedPressChanged: {
379 if (recognisedPress) {
380 wasPressed = true;
381 }
382 }
383
384 onStatusChanged: {
385 if (status !== TouchGestureArea.Recognized) {
386 if (status === TouchGestureArea.WaitingForTouch) {
387 if (wasPressed && !dragging) {
388 launcher.toggleDrawer(true);
389 }
390 }
391 wasPressed = false;
392 }
393 }
394 }
395 }
396
397 InputMethod {
398 id: inputMethod
399 objectName: "inputMethod"
400 anchors {
401 fill: parent
402 topMargin: panel.panelHeight
403 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
404 }
405 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
406 }
407
408 Loader {
409 id: greeterLoader
410 objectName: "greeterLoader"
411 anchors.fill: parent
412 sourceComponent: {
413 if (shell.mode != "shell") {
414 if (screenWindow.primary) return integratedGreeter;
415 return secondaryGreeter;
416 }
417 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
418 }
419 onLoaded: {
420 item.objectName = "greeter"
421 }
422 property bool toggleDrawerAfterUnlock: false
423 Connections {
424 target: greeter
425 onActiveChanged: {
426 if (greeter.active)
427 return
428
429 // Show drawer in case showHome() requests it
430 if (greeterLoader.toggleDrawerAfterUnlock) {
431 launcher.toggleDrawer(false);
432 greeterLoader.toggleDrawerAfterUnlock = false;
433 } else {
434 launcher.hide();
435 }
436 }
437 }
438 }
439
440 Component {
441 id: integratedGreeter
442 Greeter {
443
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
459
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
463 // left-edge drag
464 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
465
466 onTease: {
467 if (!tutorial.running) {
468 launcher.tease();
469 }
470 }
471
472 onEmergencyCall: startLockedApp("dialer-app")
473 }
474 }
475
476 Component {
477 id: secondaryGreeter
478 SecondaryGreeter {
479 hides: [launcher, panel.indicators]
480 }
481 }
482
483 Timer {
484 // See powerConnection for why this is useful
485 id: showGreeterDelayed
486 interval: 1
487 onTriggered: {
488 // Go through the dbus service, because it has checks for whether
489 // we are even allowed to lock or not.
490 DBusLomiriSessionService.PromptLock();
491 }
492 }
493
494 Connections {
495 id: callConnection
496 target: callManager
497
498 onHasCallsChanged: {
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")
509 }
510 }
511 }
512
513 Connections {
514 id: powerConnection
515 target: Powerd
516
517 onStatusChanged: {
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();
530 }
531 }
532 }
533
534 function showHome() {
535 greeter.notifyUserRequestedApp();
536
537 if (shell.mode === "greeter") {
538 SessionBroadcast.requestHomeShown(AccountsService.user);
539 } else {
540 if (!greeter.active) {
541 launcher.toggleDrawer(false);
542 } else {
543 greeterLoader.toggleDrawerAfterUnlock = true;
544 }
545 }
546 }
547
548 Item {
549 id: overlay
550 z: 10
551
552 anchors.fill: parent
553
554 Panel {
555 id: panel
556 objectName: "panel"
557 anchors.fill: parent //because this draws indicator menus
558 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
559 lightMode: shell.lightMode
560
561 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
562 minimizedPanelHeight: units.gu(3)
563 expandedPanelHeight: units.gu(7)
564 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
565
566 indicators {
567 hides: [launcher]
568 available: tutorial.panelEnabled
569 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
570 && (!greeter || !greeter.hasLockedApp)
571 && !shell.waitingOnGreeter
572 && settings.enableIndicatorMenu
573
574 model: Indicators.IndicatorsModel {
575 id: 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: {
584 load();
585 }
586 }
587 }
588
589 applicationMenus {
590 hides: [launcher]
591 available: (!greeter || !greeter.shown)
592 && !shell.waitingOnGreeter
593 && !stage.spreadShown
594 }
595
596 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
597 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
598 : false
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
605 }
606
607 Launcher {
608 id: launcher
609 objectName: "launcher"
610
611 anchors.top: parent.top
612 anchors.topMargin: inverted ? 0 : panel.panelHeight
613 anchors.bottom: parent.bottom
614 width: parent.width
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
633
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)
637
638 // The "autohideLauncher" setting is only valid in desktop mode
639 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
640
641 // The Launcher should absolutely not be locked visible under some
642 // conditions
643 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
644
645 onShowDashHome: showHome()
646 onLauncherApplicationSelected: {
647 greeter.notifyUserRequestedApp();
648 shell.activateApplication(appId);
649 }
650 onShownChanged: {
651 if (shown) {
652 panel.indicators.hide();
653 panel.applicationMenus.hide();
654 }
655 }
656 onDrawerShownChanged: {
657 if (drawerShown) {
658 panel.indicators.hide();
659 panel.applicationMenus.hide();
660 }
661 }
662 onFocusChanged: {
663 if (!focus) {
664 stage.focus = true;
665 }
666 }
667
668 GlobalShortcut {
669 shortcut: Qt.MetaModifier | Qt.Key_A
670 onTriggered: {
671 launcher.toggleDrawer(true);
672 }
673 }
674 GlobalShortcut {
675 shortcut: Qt.AltModifier | Qt.Key_F1
676 onTriggered: {
677 launcher.openForKeyboardNavigation();
678 }
679 }
680 GlobalShortcut {
681 shortcut: Qt.MetaModifier | Qt.Key_0
682 onTriggered: {
683 if (LauncherModel.get(9)) {
684 activateApplication(LauncherModel.get(9).appId);
685 }
686 }
687 }
688 Repeater {
689 model: 9
690 GlobalShortcut {
691 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
692 onTriggered: {
693 if (LauncherModel.get(index)) {
694 activateApplication(LauncherModel.get(index).appId);
695 }
696 }
697 }
698 }
699 }
700
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
708 visible: opacity > 0
709 opacity: enabled ? 0.95 : 0
710
711 Behavior on opacity {
712 LomiriNumberAnimation {}
713 }
714 }
715
716 Tutorial {
717 id: tutorial
718 objectName: "tutorial"
719 anchors.fill: parent
720
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
729 launcher: launcher
730 panel: panel
731 stage: stage
732 }
733
734 Wizard {
735 id: wizard
736 objectName: "wizard"
737 anchors.fill: parent
738 deferred: shell.mode === "greeter"
739
740 function unlockWhenDoneWithWizard() {
741 if (!active) {
742 ModemConnectivity.unlockAllModems();
743 }
744 }
745
746 Component.onCompleted: unlockWhenDoneWithWizard()
747 onActiveChanged: unlockWhenDoneWithWizard()
748 }
749
750 MouseArea { // modal notifications prevent interacting with other contents
751 anchors.fill: parent
752 visible: notifications.useModal
753 enabled: visible
754 }
755
756 Notifications {
757 id: notifications
758
759 model: NotificationBackend.Model
760 margin: units.gu(1)
761 hasMouse: shell.hasMouse
762 background: wallpaperResolver.background
763 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
764
765 y: topmostIsFullscreen ? 0 : panel.panelHeight
766 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
767
768 states: [
769 State {
770 name: "narrow"
771 when: overlay.width <= units.gu(60)
772 AnchorChanges {
773 target: notifications
774 anchors.left: parent.left
775 anchors.right: parent.right
776 }
777 },
778 State {
779 name: "wide"
780 when: overlay.width > units.gu(60)
781 AnchorChanges {
782 target: notifications
783 anchors.left: undefined
784 anchors.right: parent.right
785 }
786 PropertyChanges { target: notifications; width: units.gu(38) }
787 }
788 ]
789 }
790
791 EdgeBarrier {
792 id: rightEdgeBarrier
793 enabled: !greeter.shown
794
795 // NB: it does its own positioning according to the specified edge
796 edge: Qt.RightEdge
797
798 onPassed: {
799 panel.indicators.hide()
800 }
801
802 material: Component {
803 Item {
804 Rectangle {
805 width: parent.height
806 height: parent.width
807 rotation: 90
808 anchors.centerIn: parent
809 gradient: Gradient {
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)}
812 }
813 }
814 }
815 }
816 }
817 }
818
819 Dialogs {
820 id: dialogs
821 objectName: "dialogs"
822 anchors.fill: parent
823 visible: hasActiveDialog
824 z: overlay.z + 10
825 usageScenario: shell.usageScenario
826 hasKeyboard: shell.hasKeyboard
827 onPowerOffClicked: {
828 shutdownFadeOutRectangle.enabled = true;
829 shutdownFadeOutRectangle.visible = true;
830 shutdownFadeOut.start();
831 }
832 }
833
834 Connections {
835 target: SessionBroadcast
836 onShowHome: if (shell.mode !== "greeter") showHome()
837 }
838
839 URLDispatcher {
840 id: urlDispatcher
841 objectName: "urlDispatcher"
842 active: shell.mode === "greeter"
843 onUrlRequested: shell.activateURL(url)
844 }
845
846 ItemGrabber {
847 id: itemGrabber
848 anchors.fill: parent
849 z: dialogs.z + 10
850 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
851 Connections {
852 target: stage
853 ignoreUnknownSignals: true
854 onItemSnapshotRequested: itemGrabber.capture(item)
855 }
856 }
857
858 Timer {
859 id: cursorHidingTimer
860 interval: 3000
861 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
862 onTriggered: cursor.opacity = 0;
863 }
864
865 Cursor {
866 id: cursor
867 objectName: "cursor"
868
869 z: itemGrabber.z + 1
870 topBoundaryOffset: panel.panelHeight
871 enabled: shell.hasMouse && screenWindow.active
872 visible: enabled
873
874 property bool mouseNeverMoved: true
875 Binding {
876 target: cursor; property: "x"; value: shell.width / 2
877 when: cursor.mouseNeverMoved && cursor.visible
878 }
879 Binding {
880 target: cursor; property: "y"; value: shell.height / 2
881 when: cursor.mouseNeverMoved && cursor.visible
882 }
883
884 confiningItem: stage.itemConfiningMouseCursor
885
886 height: units.gu(3)
887
888 readonly property var previewRectangle: stage.previewRectangle.target &&
889 stage.previewRectangle.target.dragging ?
890 stage.previewRectangle : null
891
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);
897 }
898 }
899
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);
905 }
906 }
907
908 onPushedTopBoundary: {
909 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
910 previewRectangle.maximize(amount);
911 }
912 }
913 onPushedTopLeftCorner: {
914 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915 previewRectangle.maximizeTopLeft(amount);
916 }
917 }
918 onPushedTopRightCorner: {
919 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920 previewRectangle.maximizeTopRight(amount);
921 }
922 }
923 onPushedBottomLeftCorner: {
924 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
925 previewRectangle.maximizeBottomLeft(amount);
926 }
927 }
928 onPushedBottomRightCorner: {
929 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
930 previewRectangle.maximizeBottomRight(amount);
931 }
932 }
933 onPushStopped: {
934 if (previewRectangle) {
935 previewRectangle.stop();
936 }
937 }
938
939 onMouseMoved: {
940 mouseNeverMoved = false;
941 cursor.opacity = 1;
942 }
943
944 Behavior on opacity { LomiriNumberAnimation {} }
945 }
946
947 // non-visual objects
948 KeymapSwitcher {
949 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
950 }
951 BrightnessControl {}
952
953 Rectangle {
954 id: shutdownFadeOutRectangle
955 z: cursor.z + 1
956 enabled: false
957 visible: false
958 color: "black"
959 anchors.fill: parent
960 opacity: 0.0
961 NumberAnimation on opacity {
962 id: shutdownFadeOut
963 from: 0.0
964 to: 1.0
965 onStopped: {
966 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
967 DBusLomiriSessionService.shutdown();
968 }
969 }
970 }
971 }
972}