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