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 === "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 launcherLockedVisible: launcher.lockedVisible
339 topPanelHeight: panel.panelHeight
340
341 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
342 ? "phone"
343 : shell.usageScenario
344
345 mode: usageScenario == "phone" ? "staged"
346 : usageScenario == "tablet" ? "stagedWithSideStage"
347 : "windowed"
348
349 shellOrientation: shell.orientation
350 shellOrientationAngle: shell.orientationAngle
351 orientations: shell.orientations
352 nativeWidth: shell.nativeWidth
353 nativeHeight: shell.nativeHeight
354
355 allowInteractivity: (!greeter || !greeter.shown)
356 && panel.indicators.fullyClosed
357 && !notifications.useModal
358 && !launcher.takesFocus
359
360 suspended: greeter.shown
361 altTabPressed: physicalKeysMapper.altTabPressed
362 oskEnabled: shell.oskEnabled
363 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
364 panelState: panelState
365
366 onSpreadShownChanged: {
367 panel.indicators.hide();
368 panel.applicationMenus.hide();
369 }
370 }
371
372 TouchGestureArea {
373 anchors.fill: stage
374
375 minimumTouchPoints: 4
376 maximumTouchPoints: minimumTouchPoints
377
378 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
379 touchPoints.length >= minimumTouchPoints &&
380 touchPoints.length <= maximumTouchPoints
381 property bool wasPressed: false
382
383 onRecognisedPressChanged: {
384 if (recognisedPress) {
385 wasPressed = true;
386 }
387 }
388
389 onStatusChanged: {
390 if (status !== TouchGestureArea.Recognized) {
391 if (status === TouchGestureArea.WaitingForTouch) {
392 if (wasPressed && !dragging) {
393 launcher.toggleDrawer(true);
394 }
395 }
396 wasPressed = false;
397 }
398 }
399 }
400 }
401
402 InputMethod {
403 id: inputMethod
404 objectName: "inputMethod"
405 anchors {
406 fill: parent
407 topMargin: panel.panelHeight
408 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
409 }
410 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
411 }
412
413 Loader {
414 id: greeterLoader
415 objectName: "greeterLoader"
416 anchors.fill: parent
417 sourceComponent: {
418 if (shell.mode != "shell") {
419 if (screenWindow.primary) return integratedGreeter;
420 return secondaryGreeter;
421 }
422 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
423 }
424 onLoaded: {
425 item.objectName = "greeter"
426 }
427 property bool toggleDrawerAfterUnlock: false
428 Connections {
429 target: greeter
430 function onActiveChanged() {
431 if (greeter.active)
432 return
433
434 // Show drawer in case showHome() requests it
435 if (greeterLoader.toggleDrawerAfterUnlock) {
436 launcher.toggleDrawer(false);
437 greeterLoader.toggleDrawerAfterUnlock = false;
438 } else {
439 launcher.hide();
440 }
441 }
442 }
443 }
444
445 Component {
446 id: integratedGreeter
447 Greeter {
448
449 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
450 hides: [launcher, panel.indicators, panel.applicationMenus]
451 tabletMode: shell.usageScenario != "phone"
452 usageMode: shell.usageScenario
453 orientation: shell.orientation
454 forcedUnlock: wizard.active || shell.mode === "full-shell"
455 background: wallpaperResolver.background
456 backgroundSourceSize: shell.largestScreenDimension
457 hasCustomBackground: wallpaperResolver.hasCustomBackground
458 inputMethodRect: inputMethod.visibleRect
459 hasKeyboard: shell.hasKeyboard
460 allowFingerprint: !dialogs.hasActiveDialog &&
461 !notifications.topmostIsFullscreen &&
462 !panel.indicators.shown
463 panelHeight: panel.panelHeight
464
465 // avoid overlapping with Launcher's edge drag area
466 // FIXME: Fix TouchRegistry & friends and remove this workaround
467 // Issue involves launcher's DDA getting disabled on a long
468 // left-edge drag
469 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
470
471 onTease: {
472 if (!tutorial.running) {
473 launcher.tease();
474 }
475 }
476
477 onEmergencyCall: startLockedApp("dialer-app")
478 }
479 }
480
481 Component {
482 id: secondaryGreeter
483 SecondaryGreeter {
484 hides: [launcher, panel.indicators]
485 }
486 }
487
488 Timer {
489 // See powerConnection for why this is useful
490 id: showGreeterDelayed
491 interval: 1
492 onTriggered: {
493 // Go through the dbus service, because it has checks for whether
494 // we are even allowed to lock or not.
495 DBusLomiriSessionService.PromptLock();
496 }
497 }
498
499 Connections {
500 id: callConnection
501 target: callManager
502
503 function onHasCallsChanged() {
504 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
505 // We just received an incoming call while locked. The
506 // indicator will have already launched dialer-app for us, but
507 // there is a race between "hasCalls" changing and the dialer
508 // starting up. So in case we lose that race, we'll start/
509 // focus the dialer ourselves here too. Even if the indicator
510 // didn't launch the dialer for some reason (or maybe a call
511 // started via some other means), if an active call is
512 // happening, we want to be in the dialer.
513 startLockedApp("dialer-app")
514 }
515 }
516 }
517
518 Connections {
519 id: powerConnection
520 target: Powerd
521
522 function onStatusChanged(reason) {
523 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
524 !callManager.hasCalls && !wizard.active) {
525 // We don't want to simply call greeter.showNow() here, because
526 // that will take too long. Qt will delay button event
527 // handling until the greeter is done loading and may think the
528 // user held down the power button the whole time, leading to a
529 // power dialog being shown. Instead, delay showing the
530 // greeter until we've finished handling the event. We could
531 // make the greeter load asynchronously instead, but that
532 // introduces a whole host of timing issues, especially with
533 // its animations. So this is simpler.
534 showGreeterDelayed.start();
535 }
536 }
537 }
538
539 function showHome() {
540 greeter.notifyUserRequestedApp();
541
542 if (shell.mode === "greeter") {
543 SessionBroadcast.requestHomeShown(AccountsService.user);
544 } else {
545 if (!greeter.active) {
546 launcher.toggleDrawer(false);
547 } else {
548 greeterLoader.toggleDrawerAfterUnlock = true;
549 }
550 }
551 }
552
553 Item {
554 id: overlay
555 z: 10
556
557 anchors.fill: parent
558
559 SwipeArea {
560 objectName: "fullscreenSwipeDown"
561 enabled: panel.state === "offscreen"
562 direction: SwipeArea.Downwards
563 immediateRecognition: false
564 height: units.gu(2)
565 anchors {
566 top: parent.top
567 left: parent.left
568 right: parent.right
569 }
570 onDraggingChanged: {
571 if (dragging) {
572 panel.temporarilyShow()
573 }
574 }
575 }
576
577 Panel {
578 id: panel
579 objectName: "panel"
580 anchors.fill: parent //because this draws indicator menus
581 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
582 lightMode: shell.lightMode
583
584 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
585 minimizedPanelHeight: units.gu(3)
586 expandedPanelHeight: units.gu(7)
587 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
588
589 indicators {
590 hides: [launcher]
591 available: tutorial.panelEnabled
592 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
593 && (!greeter || !greeter.hasLockedApp)
594 && !shell.waitingOnGreeter
595 && settings.enableIndicatorMenu
596
597 model: Indicators.IndicatorsModel {
598 id: indicatorsModel
599 // tablet and phone both use the same profile
600 // FIXME: use just "phone" for greeter too, but first fix
601 // greeter app launching to either load the app inside the
602 // greeter or tell the session to load the app. This will
603 // involve taking the url-dispatcher dbus name and using
604 // SessionBroadcast to tell the session.
605 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
606 Component.onCompleted: {
607 load();
608 }
609 }
610 }
611
612 applicationMenus {
613 hides: [launcher]
614 available: (!greeter || !greeter.shown)
615 && !shell.waitingOnGreeter
616 && !stage.spreadShown
617 }
618
619 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
620 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
621 : false
622 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
623 || greeter.hasLockedApp
624 greeterShown: greeter && greeter.shown
625 hasKeyboard: shell.hasKeyboard
626 panelState: panelState
627 supportsMultiColorLed: shell.supportsMultiColorLed
628 }
629
630 Launcher {
631 id: launcher
632 objectName: "launcher"
633
634 anchors.top: parent.top
635 anchors.topMargin: inverted ? 0 : panel.panelHeight
636 anchors.bottom: parent.bottom
637 width: parent.width
638 dragAreaWidth: shell.edgeSize
639 available: tutorial.launcherEnabled
640 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
641 && !greeter.hasLockedApp
642 && !shell.waitingOnGreeter
643 && shell.mode !== "greeter"
644 visible: shell.mode !== "greeter"
645 inverted: shell.usageScenario !== "desktop"
646 superPressed: physicalKeysMapper.superPressed
647 superTabPressed: physicalKeysMapper.superTabPressed
648 panelWidth: units.gu(settings.launcherWidth)
649 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
650 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
651 topPanelHeight: panel.panelHeight
652 lightMode: shell.lightMode
653 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
654 privateMode: greeter.active
655 background: wallpaperResolver.background
656
657 // It can be assumed that the Launcher and Panel would overlap if
658 // the Panel is open and taking up the full width of the shell
659 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
660
661 // The "autohideLauncher" setting is only valid in desktop mode
662 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
663
664 // The Launcher should absolutely not be locked visible under some
665 // conditions
666 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
667
668 onShowDashHome: showHome()
669 onLauncherApplicationSelected: {
670 greeter.notifyUserRequestedApp();
671 shell.activateApplication(appId);
672 }
673 onShownChanged: {
674 if (shown) {
675 panel.indicators.hide();
676 panel.applicationMenus.hide();
677 }
678 }
679 onDrawerShownChanged: {
680 if (drawerShown) {
681 panel.indicators.hide();
682 panel.applicationMenus.hide();
683 }
684 }
685 onFocusChanged: {
686 if (!focus) {
687 stage.focus = true;
688 }
689 }
690
691 GlobalShortcut {
692 shortcut: Qt.MetaModifier | Qt.Key_A
693 onTriggered: {
694 launcher.toggleDrawer(true);
695 }
696 }
697 GlobalShortcut {
698 shortcut: Qt.AltModifier | Qt.Key_F1
699 onTriggered: {
700 launcher.openForKeyboardNavigation();
701 }
702 }
703 GlobalShortcut {
704 shortcut: Qt.MetaModifier | Qt.Key_0
705 onTriggered: {
706 if (LauncherModel.get(9)) {
707 activateApplication(LauncherModel.get(9).appId);
708 }
709 }
710 }
711 Repeater {
712 model: 9
713 GlobalShortcut {
714 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
715 onTriggered: {
716 if (LauncherModel.get(index)) {
717 activateApplication(LauncherModel.get(index).appId);
718 }
719 }
720 }
721 }
722 }
723
724 KeyboardShortcutsOverlay {
725 objectName: "shortcutsOverlay"
726 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
727 && height < parent.height - padding - panel.panelHeight
728 anchors.centerIn: parent
729 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
730 anchors.verticalCenterOffset: panel.panelHeight/2
731 visible: opacity > 0
732 opacity: enabled ? 0.95 : 0
733
734 Behavior on opacity {
735 LomiriNumberAnimation {}
736 }
737 }
738
739 Tutorial {
740 id: tutorial
741 objectName: "tutorial"
742 anchors.fill: parent
743
744 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
745 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
746 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
747 inputMethod.visible ||
748 (launcher.shown && !launcher.lockedVisible) ||
749 panel.indicators.shown || stage.rightEdgeDragProgress > 0
750 usageScenario: shell.usageScenario
751 lastInputTimestamp: inputFilter.lastInputTimestamp
752 launcher: launcher
753 panel: panel
754 stage: stage
755 }
756
757 Wizard {
758 id: wizard
759 objectName: "wizard"
760 anchors.fill: parent
761 deferred: shell.mode === "greeter"
762
763 function unlockWhenDoneWithWizard() {
764 if (!active) {
765 ModemConnectivity.unlockAllModems();
766 }
767 }
768
769 Component.onCompleted: unlockWhenDoneWithWizard()
770 onActiveChanged: unlockWhenDoneWithWizard()
771 }
772
773 MouseArea { // modal notifications prevent interacting with other contents
774 anchors.fill: parent
775 visible: notifications.useModal
776 enabled: visible
777 }
778
779 Notifications {
780 id: notifications
781
782 model: NotificationBackend.Model
783 margin: units.gu(1)
784 hasMouse: shell.hasMouse
785 background: wallpaperResolver.background
786 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
787
788 y: topmostIsFullscreen ? 0 : panel.panelHeight
789 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
790
791 states: [
792 State {
793 name: "narrow"
794 when: overlay.width <= units.gu(60)
795 AnchorChanges {
796 target: notifications
797 anchors.left: parent.left
798 anchors.right: parent.right
799 }
800 },
801 State {
802 name: "wide"
803 when: overlay.width > units.gu(60)
804 AnchorChanges {
805 target: notifications
806 anchors.left: undefined
807 anchors.right: parent.right
808 }
809 PropertyChanges { target: notifications; width: units.gu(38) }
810 }
811 ]
812 }
813
814 EdgeBarrier {
815 id: rightEdgeBarrier
816 enabled: !greeter.shown
817
818 // NB: it does its own positioning according to the specified edge
819 edge: Qt.RightEdge
820
821 onPassed: {
822 panel.indicators.hide()
823 }
824
825 material: Component {
826 Item {
827 Rectangle {
828 width: parent.height
829 height: parent.width
830 rotation: 90
831 anchors.centerIn: parent
832 gradient: Gradient {
833 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
834 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
835 }
836 }
837 }
838 }
839 }
840 }
841
842 Dialogs {
843 id: dialogs
844 objectName: "dialogs"
845 anchors.fill: parent
846 visible: hasActiveDialog
847 z: overlay.z + 10
848 usageScenario: shell.usageScenario
849 hasKeyboard: shell.hasKeyboard
850 onPowerOffClicked: {
851 shutdownFadeOutRectangle.enabled = true;
852 shutdownFadeOutRectangle.visible = true;
853 shutdownFadeOut.start();
854 }
855 }
856
857 Connections {
858 target: SessionBroadcast
859 function onShowHome() { if (shell.mode !== "greeter") showHome() }
860 }
861
862 URLDispatcher {
863 id: urlDispatcher
864 objectName: "urlDispatcher"
865 active: shell.mode === "greeter"
866 onUrlRequested: shell.activateURL(url)
867 }
868
869 ItemGrabber {
870 id: itemGrabber
871 anchors.fill: parent
872 z: dialogs.z + 10
873 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
874 Connections {
875 target: stage
876 ignoreUnknownSignals: true
877 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
878 }
879 }
880
881 Timer {
882 id: cursorHidingTimer
883 interval: 3000
884 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
885 onTriggered: cursor.opacity = 0;
886 }
887
888 Cursor {
889 id: cursor
890 objectName: "cursor"
891
892 z: itemGrabber.z + 1
893 topBoundaryOffset: panel.panelHeight
894 active: shell.hasMouse
895 visible: enabled
896
897 property bool mouseNeverMoved: true
898 Binding {
899 target: cursor; property: "x"; value: shell.width / 2
900 restoreMode: Binding.RestoreBinding
901 when: cursor.mouseNeverMoved && cursor.visible
902 }
903 Binding {
904 target: cursor; property: "y"; value: shell.height / 2
905 restoreMode: Binding.RestoreBinding
906 when: cursor.mouseNeverMoved && cursor.visible
907 }
908
909 confiningItem: stage.itemConfiningMouseCursor
910
911 height: units.gu(3)
912
913 readonly property var previewRectangle: stage.previewRectangle.target &&
914 stage.previewRectangle.target.dragging ?
915 stage.previewRectangle : null
916
917 onPushedLeftBoundary: {
918 if (buttons === Qt.NoButton) {
919 launcher.pushEdge(amount);
920 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
921 previewRectangle.maximizeLeft(amount);
922 }
923 }
924
925 onPushedRightBoundary: {
926 if (buttons === Qt.NoButton) {
927 rightEdgeBarrier.push(amount);
928 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
929 previewRectangle.maximizeRight(amount);
930 }
931 }
932
933 onPushedTopBoundary: {
934 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
935 previewRectangle.maximize(amount);
936 }
937 }
938 onPushedTopLeftCorner: {
939 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
940 previewRectangle.maximizeTopLeft(amount);
941 }
942 }
943 onPushedTopRightCorner: {
944 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
945 previewRectangle.maximizeTopRight(amount);
946 }
947 }
948 onPushedBottomLeftCorner: {
949 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
950 previewRectangle.maximizeBottomLeft(amount);
951 }
952 }
953 onPushedBottomRightCorner: {
954 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
955 previewRectangle.maximizeBottomRight(amount);
956 }
957 }
958 onPushStopped: {
959 if (previewRectangle) {
960 previewRectangle.stop();
961 }
962 }
963
964 onMouseMoved: {
965 mouseNeverMoved = false;
966 cursor.opacity = 1;
967 }
968
969 Behavior on opacity { LomiriNumberAnimation {} }
970 }
971
972 // non-visual objects
973 KeymapSwitcher {
974 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
975 }
976 BrightnessControl {}
977
978 Rectangle {
979 id: shutdownFadeOutRectangle
980 z: cursor.z + 1
981 enabled: false
982 visible: false
983 color: "black"
984 anchors.fill: parent
985 opacity: 0.0
986 NumberAnimation on opacity {
987 id: shutdownFadeOut
988 from: 0.0
989 to: 1.0
990 onStopped: {
991 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
992 DBusLomiriSessionService.shutdown();
993 }
994 }
995 }
996 }
997}