Lomiri
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 theme.name: "Lomiri.Components.Themes.SuruDark"
53
54 // to be set from outside
55 property int orientationAngle: 0
56 property int orientation
57 property Orientations orientations
58 property real nativeWidth
59 property real nativeHeight
60 property alias panelAreaShowProgress: panel.panelAreaShowProgress
61 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
62 property string mode: "full-greeter"
63 property alias oskEnabled: inputMethod.enabled
64 function updateFocusedAppOrientation() {
65 stage.updateFocusedAppOrientation();
66 }
67 function updateFocusedAppOrientationAnimated() {
68 stage.updateFocusedAppOrientationAnimated();
69 }
70 property bool hasMouse: false
71 property bool hasKeyboard: false
72 property bool hasTouchscreen: false
73 property bool supportsMultiColorLed: true
74
75 // The largest dimension, in pixels, of all of the screens this Shell is
76 // operating on.
77 // If a script sets the shell to 240x320 when it was 320x240, we could
78 // end up in a situation where our dimensions are 240x240 for a short time.
79 // Notifying the Wallpaper of both events would make it reload the image
80 // twice. So, we use a Binding { delayed: true }.
81 property real largestScreenDimension
82 Binding {
83 target: shell
84 delayed: true
85 property: "largestScreenDimension"
86 value: Math.max(nativeWidth, nativeHeight)
87 }
88
89 // Used by tests
90 property alias lightIndicators: indicatorsModel.light
91
92 // to be read from outside
93 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
94
95 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
96 && stage.orientationChangesEnabled
97 && (!greeter || !greeter.animating)
98
99 readonly property bool showingGreeter: greeter && greeter.shown
100
101 property bool startingUp: true
102 Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
103
104 property int supportedOrientations: {
105 if (startingUp) {
106 // Ensure we don't rotate during start up
107 return Qt.PrimaryOrientation;
108 } else if (showingGreeter || notifications.topmostIsFullscreen) {
109 return Qt.PrimaryOrientation;
110 } else {
111 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
112 }
113 }
114
115 readonly property var mainApp: stage.mainApp
116
117 readonly property var topLevelSurfaceList: {
118 if (!WMScreen.currentWorkspace) return null;
119 return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
120 }
121
122 onMainAppChanged: {
123 _onMainAppChanged((mainApp ? mainApp.appId : ""));
124 }
125 Connections {
126 target: ApplicationManager
127 onFocusRequested: {
128 if (shell.mainApp && shell.mainApp.appId === appId) {
129 _onMainAppChanged(appId);
130 }
131 }
132 }
133
134 // Calls attention back to the most important thing that's been focused
135 // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
136 // goes over everything if it is locked)
137 // Must be called whenever app focus changes occur, even if the focus change
138 // is "nothing is focused". In that case, call with appId = ""
139 function _onMainAppChanged(appId) {
140
141 if (appId !== "") {
142 if (wizard.active) {
143 // If this happens on first boot, we may be in the
144 // wizard while receiving a call. A call is more
145 // important than the wizard so just bail out of it.
146 wizard.hide();
147 }
148
149 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
150 // If we are in the middle of a call, make dialer lockedApp. The
151 // Greeter will show it when it's notified of the focus.
152 // This can happen if user backs out of dialer back to greeter, then
153 // launches dialer again.
154 greeter.lockedApp = appId;
155 }
156
157 panel.indicators.hide();
158 launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
159 }
160
161 // *Always* make sure the greeter knows that the focused app changed
162 if (greeter) greeter.notifyAppFocusRequested(appId);
163 }
164
165 // For autopilot consumption
166 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
167
168 // Note when greeter is waiting on PAM, so that we can disable edges until
169 // we know which user data to show and whether the session is locked.
170 readonly property bool waitingOnGreeter: greeter && greeter.waiting
171
172 // True when the user is logged in with no apps running
173 readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
174
175 onAtDesktopChanged: {
176 if (atDesktop && stage) {
177 stage.closeSpread();
178 }
179 }
180
181 property real edgeSize: units.gu(settings.edgeDragWidth)
182
183 WallpaperResolver {
184 id: wallpaperResolver
185 objectName: "wallpaperResolver"
186
187 readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
188 readonly property bool hasCustomBackground: background != defaultBackground
189
190 GSettings {
191 id: backgroundSettings
192 schema.id: "org.gnome.desktop.background"
193 }
194
195 candidates: [
196 AccountsService.backgroundFile,
197 backgroundSettings.pictureUri,
198 defaultBackground
199 ]
200 }
201
202 readonly property alias greeter: greeterLoader.item
203
204 function activateApplication(appId) {
205 topLevelSurfaceList.pendingActivation();
206
207 // Either open the app in our own session, or -- if we're acting as a
208 // greeter -- ask the user's session to open it for us.
209 if (shell.mode === "greeter") {
210 activateURL("application:///" + appId + ".desktop");
211 } else {
212 startApp(appId);
213 }
214 stage.focus = true;
215 }
216
217 function activateURL(url) {
218 SessionBroadcast.requestUrlStart(AccountsService.user, url);
219 greeter.notifyUserRequestedApp();
220 panel.indicators.hide();
221 }
222
223 function startApp(appId) {
224 if (!ApplicationManager.findApplication(appId)) {
225 ApplicationManager.startApplication(appId);
226 }
227 ApplicationManager.requestFocusApplication(appId);
228 stage.closeSpread();
229 }
230
231 function startLockedApp(app) {
232 topLevelSurfaceList.pendingActivation();
233
234 if (greeter.locked) {
235 greeter.lockedApp = app;
236 }
237 startApp(app); // locked apps are always in our same session
238 }
239
240 Binding {
241 target: LauncherModel
242 property: "applicationManager"
243 value: ApplicationManager
244 }
245
246 Component.onCompleted: {
247 finishStartUpTimer.start();
248 }
249
250 VolumeControl {
251 id: volumeControl
252 }
253
254 PhysicalKeysMapper {
255 id: physicalKeysMapper
256 objectName: "physicalKeysMapper"
257
258 onPowerKeyLongPressed: dialogs.showPowerDialog();
259 onVolumeDownTriggered: volumeControl.volumeDown();
260 onVolumeUpTriggered: volumeControl.volumeUp();
261 onScreenshotTriggered: itemGrabber.capture(shell);
262 }
263
264 GlobalShortcut {
265 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
266 }
267
268 WindowInputFilter {
269 id: inputFilter
270 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
271 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
272 }
273
274 WindowInputMonitor {
275 objectName: "windowInputMonitor"
276 onHomeKeyActivated: {
277 // Ignore when greeter is active, to avoid pocket presses
278 if (!greeter.active) {
279 launcher.toggleDrawer(/* focusInputField */ false,
280 /* onlyOpen */ false,
281 /* alsoToggleLauncher */ true);
282 }
283 }
284 onTouchBegun: { cursor.opacity = 0; }
285 onTouchEnded: {
286 // move the (hidden) cursor to the last known touch position
287 var mappedCoords = mapFromItem(null, pos.x, pos.y);
288 cursor.x = mappedCoords.x;
289 cursor.y = mappedCoords.y;
290 cursor.mouseNeverMoved = false;
291 }
292 }
293
294 AvailableDesktopArea {
295 id: availableDesktopAreaItem
296 anchors.fill: parent
297 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
298 anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
299 }
300
301 GSettings {
302 id: settings
303 schema.id: "com.lomiri.Shell"
304 }
305
306 PanelState {
307 id: panelState
308 objectName: "panelState"
309 }
310
311 Item {
312 id: stages
313 objectName: "stages"
314 width: parent.width
315 height: parent.height
316
317 Stage {
318 id: stage
319 objectName: "stage"
320 anchors.fill: parent
321 focus: true
322
323 dragAreaWidth: shell.edgeSize
324 background: wallpaperResolver.background
325 backgroundSourceSize: shell.largestScreenDimension
326
327 applicationManager: ApplicationManager
328 topLevelSurfaceList: shell.topLevelSurfaceList
329 inputMethodRect: inputMethod.visibleRect
330 rightEdgePushProgress: rightEdgeBarrier.progress
331 availableDesktopArea: availableDesktopAreaItem
332 launcherLeftMargin: launcher.visibleWidth
333
334 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
335 ? "phone"
336 : shell.usageScenario
337
338 mode: usageScenario == "phone" ? "staged"
339 : usageScenario == "tablet" ? "stagedWithSideStage"
340 : "windowed"
341
342 shellOrientation: shell.orientation
343 shellOrientationAngle: shell.orientationAngle
344 orientations: shell.orientations
345 nativeWidth: shell.nativeWidth
346 nativeHeight: shell.nativeHeight
347
348 allowInteractivity: (!greeter || !greeter.shown)
349 && panel.indicators.fullyClosed
350 && !notifications.useModal
351 && !launcher.takesFocus
352
353 suspended: greeter.shown
354 altTabPressed: physicalKeysMapper.altTabPressed
355 oskEnabled: shell.oskEnabled
356 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
357 panelState: panelState
358
359 onSpreadShownChanged: {
360 panel.indicators.hide();
361 panel.applicationMenus.hide();
362 }
363 }
364
365 TouchGestureArea {
366 anchors.fill: stage
367
368 minimumTouchPoints: 4
369 maximumTouchPoints: minimumTouchPoints
370
371 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
372 touchPoints.length >= minimumTouchPoints &&
373 touchPoints.length <= maximumTouchPoints
374 property bool wasPressed: false
375
376 onRecognisedPressChanged: {
377 if (recognisedPress) {
378 wasPressed = true;
379 }
380 }
381
382 onStatusChanged: {
383 if (status !== TouchGestureArea.Recognized) {
384 if (status === TouchGestureArea.WaitingForTouch) {
385 if (wasPressed && !dragging) {
386 launcher.toggleDrawer(true);
387 }
388 }
389 wasPressed = false;
390 }
391 }
392 }
393 }
394
395 InputMethod {
396 id: inputMethod
397 objectName: "inputMethod"
398 anchors {
399 fill: parent
400 topMargin: panel.panelHeight
401 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
402 }
403 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
404 }
405
406 Loader {
407 id: greeterLoader
408 objectName: "greeterLoader"
409 anchors.fill: parent
410 sourceComponent: {
411 if (shell.mode != "shell") {
412 if (screenWindow.primary) return integratedGreeter;
413 return secondaryGreeter;
414 }
415 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
416 }
417 onLoaded: {
418 item.objectName = "greeter"
419 }
420 property bool toggleDrawerAfterUnlock: false
421 Connections {
422 target: greeter
423 onActiveChanged: {
424 if (greeter.active)
425 return
426
427 // Show drawer in case showHome() requests it
428 if (greeterLoader.toggleDrawerAfterUnlock) {
429 launcher.toggleDrawer(false);
430 greeterLoader.toggleDrawerAfterUnlock = false;
431 } else {
432 launcher.hide();
433 }
434 }
435 }
436 }
437
438 Component {
439 id: integratedGreeter
440 Greeter {
441
442 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
443 hides: [launcher, panel.indicators, panel.applicationMenus]
444 tabletMode: shell.usageScenario != "phone"
445 forcedUnlock: wizard.active || shell.mode === "full-shell"
446 background: wallpaperResolver.background
447 backgroundSourceSize: shell.largestScreenDimension
448 hasCustomBackground: wallpaperResolver.hasCustomBackground
449 inputMethodRect: inputMethod.visibleRect
450 hasKeyboard: shell.hasKeyboard
451 allowFingerprint: !dialogs.hasActiveDialog &&
452 !notifications.topmostIsFullscreen &&
453 !panel.indicators.shown
454 panelHeight: panel.panelHeight
455
456 // avoid overlapping with Launcher's edge drag area
457 // FIXME: Fix TouchRegistry & friends and remove this workaround
458 // Issue involves launcher's DDA getting disabled on a long
459 // left-edge drag
460 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
461
462 onTease: {
463 if (!tutorial.running) {
464 launcher.tease();
465 }
466 }
467
468 onEmergencyCall: startLockedApp("dialer-app")
469 }
470 }
471
472 Component {
473 id: secondaryGreeter
474 SecondaryGreeter {
475 hides: [launcher, panel.indicators]
476 }
477 }
478
479 Timer {
480 // See powerConnection for why this is useful
481 id: showGreeterDelayed
482 interval: 1
483 onTriggered: {
484 // Go through the dbus service, because it has checks for whether
485 // we are even allowed to lock or not.
486 DBusLomiriSessionService.PromptLock();
487 }
488 }
489
490 Connections {
491 id: callConnection
492 target: callManager
493
494 onHasCallsChanged: {
495 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
496 // We just received an incoming call while locked. The
497 // indicator will have already launched dialer-app for us, but
498 // there is a race between "hasCalls" changing and the dialer
499 // starting up. So in case we lose that race, we'll start/
500 // focus the dialer ourselves here too. Even if the indicator
501 // didn't launch the dialer for some reason (or maybe a call
502 // started via some other means), if an active call is
503 // happening, we want to be in the dialer.
504 startLockedApp("dialer-app")
505 }
506 }
507 }
508
509 Connections {
510 id: powerConnection
511 target: Powerd
512
513 onStatusChanged: {
514 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
515 !callManager.hasCalls && !wizard.active) {
516 // We don't want to simply call greeter.showNow() here, because
517 // that will take too long. Qt will delay button event
518 // handling until the greeter is done loading and may think the
519 // user held down the power button the whole time, leading to a
520 // power dialog being shown. Instead, delay showing the
521 // greeter until we've finished handling the event. We could
522 // make the greeter load asynchronously instead, but that
523 // introduces a whole host of timing issues, especially with
524 // its animations. So this is simpler.
525 showGreeterDelayed.start();
526 }
527 }
528 }
529
530 function showHome() {
531 greeter.notifyUserRequestedApp();
532
533 if (shell.mode === "greeter") {
534 SessionBroadcast.requestHomeShown(AccountsService.user);
535 } else {
536 if (!greeter.active) {
537 launcher.toggleDrawer(false);
538 } else {
539 greeterLoader.toggleDrawerAfterUnlock = true;
540 }
541 }
542 }
543
544 Item {
545 id: overlay
546 z: 10
547
548 anchors.fill: parent
549
550 Panel {
551 id: panel
552 objectName: "panel"
553 anchors.fill: parent //because this draws indicator menus
554 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
555
556 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
557 minimizedPanelHeight: units.gu(3)
558 expandedPanelHeight: units.gu(7)
559 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
560
561 indicators {
562 hides: [launcher]
563 available: tutorial.panelEnabled
564 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
565 && (!greeter || !greeter.hasLockedApp)
566 && !shell.waitingOnGreeter
567 && settings.enableIndicatorMenu
568
569 model: Indicators.IndicatorsModel {
570 id: indicatorsModel
571 // tablet and phone both use the same profile
572 // FIXME: use just "phone" for greeter too, but first fix
573 // greeter app launching to either load the app inside the
574 // greeter or tell the session to load the app. This will
575 // involve taking the url-dispatcher dbus name and using
576 // SessionBroadcast to tell the session.
577 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
578 Component.onCompleted: {
579 load();
580 }
581 }
582 }
583
584 applicationMenus {
585 hides: [launcher]
586 available: (!greeter || !greeter.shown)
587 && !shell.waitingOnGreeter
588 && !stage.spreadShown
589 }
590
591 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
592 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
593 : false
594 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
595 || greeter.hasLockedApp
596 greeterShown: greeter && greeter.shown
597 hasKeyboard: shell.hasKeyboard
598 panelState: panelState
599 supportsMultiColorLed: shell.supportsMultiColorLed
600 }
601
602 Launcher {
603 id: launcher
604 objectName: "launcher"
605
606 anchors.top: parent.top
607 anchors.topMargin: inverted ? 0 : panel.panelHeight
608 anchors.bottom: parent.bottom
609 width: parent.width
610 dragAreaWidth: shell.edgeSize
611 available: tutorial.launcherEnabled
612 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
613 && !greeter.hasLockedApp
614 && !shell.waitingOnGreeter
615 inverted: shell.usageScenario !== "desktop"
616 superPressed: physicalKeysMapper.superPressed
617 superTabPressed: physicalKeysMapper.superTabPressed
618 panelWidth: units.gu(settings.launcherWidth)
619 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
620 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
621 topPanelHeight: panel.panelHeight
622 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
623 privateMode: greeter.active
624 background: wallpaperResolver.background
625
626 // It can be assumed that the Launcher and Panel would overlap if
627 // the Panel is open and taking up the full width of the shell
628 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
629
630 // The "autohideLauncher" setting is only valid in desktop mode
631 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
632
633 // The Launcher should absolutely not be locked visible under some
634 // conditions
635 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
636
637 onShowDashHome: showHome()
638 onLauncherApplicationSelected: {
639 greeter.notifyUserRequestedApp();
640 shell.activateApplication(appId);
641 }
642 onShownChanged: {
643 if (shown) {
644 panel.indicators.hide();
645 panel.applicationMenus.hide();
646 }
647 }
648 onDrawerShownChanged: {
649 if (drawerShown) {
650 panel.indicators.hide();
651 panel.applicationMenus.hide();
652 }
653 }
654 onFocusChanged: {
655 if (!focus) {
656 stage.focus = true;
657 }
658 }
659
660 GlobalShortcut {
661 shortcut: Qt.MetaModifier | Qt.Key_A
662 onTriggered: {
663 launcher.toggleDrawer(true);
664 }
665 }
666 GlobalShortcut {
667 shortcut: Qt.AltModifier | Qt.Key_F1
668 onTriggered: {
669 launcher.openForKeyboardNavigation();
670 }
671 }
672 GlobalShortcut {
673 shortcut: Qt.MetaModifier | Qt.Key_0
674 onTriggered: {
675 if (LauncherModel.get(9)) {
676 activateApplication(LauncherModel.get(9).appId);
677 }
678 }
679 }
680 Repeater {
681 model: 9
682 GlobalShortcut {
683 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
684 onTriggered: {
685 if (LauncherModel.get(index)) {
686 activateApplication(LauncherModel.get(index).appId);
687 }
688 }
689 }
690 }
691 }
692
693 KeyboardShortcutsOverlay {
694 objectName: "shortcutsOverlay"
695 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
696 && height < parent.height - padding - panel.panelHeight
697 anchors.centerIn: parent
698 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
699 anchors.verticalCenterOffset: panel.panelHeight/2
700 visible: opacity > 0
701 opacity: enabled ? 0.95 : 0
702
703 Behavior on opacity {
704 LomiriNumberAnimation {}
705 }
706 }
707
708 Tutorial {
709 id: tutorial
710 objectName: "tutorial"
711 anchors.fill: parent
712
713 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
714 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
715 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
716 inputMethod.visible ||
717 (launcher.shown && !launcher.lockedVisible) ||
718 panel.indicators.shown || stage.rightEdgeDragProgress > 0
719 usageScenario: shell.usageScenario
720 lastInputTimestamp: inputFilter.lastInputTimestamp
721 launcher: launcher
722 panel: panel
723 stage: stage
724 }
725
726 Wizard {
727 id: wizard
728 objectName: "wizard"
729 anchors.fill: parent
730 deferred: shell.mode === "greeter"
731
732 function unlockWhenDoneWithWizard() {
733 if (!active) {
734 ModemConnectivity.unlockAllModems();
735 }
736 }
737
738 Component.onCompleted: unlockWhenDoneWithWizard()
739 onActiveChanged: unlockWhenDoneWithWizard()
740 }
741
742 MouseArea { // modal notifications prevent interacting with other contents
743 anchors.fill: parent
744 visible: notifications.useModal
745 enabled: visible
746 }
747
748 Notifications {
749 id: notifications
750
751 model: NotificationBackend.Model
752 margin: units.gu(1)
753 hasMouse: shell.hasMouse
754 background: wallpaperResolver.background
755
756 y: topmostIsFullscreen ? 0 : panel.panelHeight
757 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
758
759 states: [
760 State {
761 name: "narrow"
762 when: overlay.width <= units.gu(60)
763 AnchorChanges {
764 target: notifications
765 anchors.left: parent.left
766 anchors.right: parent.right
767 }
768 },
769 State {
770 name: "wide"
771 when: overlay.width > units.gu(60)
772 AnchorChanges {
773 target: notifications
774 anchors.left: undefined
775 anchors.right: parent.right
776 }
777 PropertyChanges { target: notifications; width: units.gu(38) }
778 }
779 ]
780 }
781
782 EdgeBarrier {
783 id: rightEdgeBarrier
784 enabled: !greeter.shown
785
786 // NB: it does its own positioning according to the specified edge
787 edge: Qt.RightEdge
788
789 onPassed: {
790 panel.indicators.hide()
791 }
792
793 material: Component {
794 Item {
795 Rectangle {
796 width: parent.height
797 height: parent.width
798 rotation: 90
799 anchors.centerIn: parent
800 gradient: Gradient {
801 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
802 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
803 }
804 }
805 }
806 }
807 }
808 }
809
810 Dialogs {
811 id: dialogs
812 objectName: "dialogs"
813 anchors.fill: parent
814 visible: hasActiveDialog
815 z: overlay.z + 10
816 usageScenario: shell.usageScenario
817 hasKeyboard: shell.hasKeyboard
818 onPowerOffClicked: {
819 shutdownFadeOutRectangle.enabled = true;
820 shutdownFadeOutRectangle.visible = true;
821 shutdownFadeOut.start();
822 }
823 }
824
825 Connections {
826 target: SessionBroadcast
827 onShowHome: if (shell.mode !== "greeter") showHome()
828 }
829
830 URLDispatcher {
831 id: urlDispatcher
832 objectName: "urlDispatcher"
833 active: shell.mode === "greeter"
834 onUrlRequested: shell.activateURL(url)
835 }
836
837 ItemGrabber {
838 id: itemGrabber
839 anchors.fill: parent
840 z: dialogs.z + 10
841 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
842 Connections {
843 target: stage
844 ignoreUnknownSignals: true
845 onItemSnapshotRequested: itemGrabber.capture(item)
846 }
847 }
848
849 Timer {
850 id: cursorHidingTimer
851 interval: 3000
852 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
853 onTriggered: cursor.opacity = 0;
854 }
855
856 Cursor {
857 id: cursor
858 objectName: "cursor"
859
860 z: itemGrabber.z + 1
861 topBoundaryOffset: panel.panelHeight
862 enabled: shell.hasMouse && screenWindow.active
863 visible: enabled
864
865 property bool mouseNeverMoved: true
866 Binding {
867 target: cursor; property: "x"; value: shell.width / 2
868 when: cursor.mouseNeverMoved && cursor.visible
869 }
870 Binding {
871 target: cursor; property: "y"; value: shell.height / 2
872 when: cursor.mouseNeverMoved && cursor.visible
873 }
874
875 confiningItem: stage.itemConfiningMouseCursor
876
877 height: units.gu(3)
878
879 readonly property var previewRectangle: stage.previewRectangle.target &&
880 stage.previewRectangle.target.dragging ?
881 stage.previewRectangle : null
882
883 onPushedLeftBoundary: {
884 if (buttons === Qt.NoButton) {
885 launcher.pushEdge(amount);
886 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
887 previewRectangle.maximizeLeft(amount);
888 }
889 }
890
891 onPushedRightBoundary: {
892 if (buttons === Qt.NoButton) {
893 rightEdgeBarrier.push(amount);
894 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
895 previewRectangle.maximizeRight(amount);
896 }
897 }
898
899 onPushedTopBoundary: {
900 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
901 previewRectangle.maximize(amount);
902 }
903 }
904 onPushedTopLeftCorner: {
905 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
906 previewRectangle.maximizeTopLeft(amount);
907 }
908 }
909 onPushedTopRightCorner: {
910 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
911 previewRectangle.maximizeTopRight(amount);
912 }
913 }
914 onPushedBottomLeftCorner: {
915 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
916 previewRectangle.maximizeBottomLeft(amount);
917 }
918 }
919 onPushedBottomRightCorner: {
920 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
921 previewRectangle.maximizeBottomRight(amount);
922 }
923 }
924 onPushStopped: {
925 if (previewRectangle) {
926 previewRectangle.stop();
927 }
928 }
929
930 onMouseMoved: {
931 mouseNeverMoved = false;
932 cursor.opacity = 1;
933 }
934
935 Behavior on opacity { LomiriNumberAnimation {} }
936 }
937
938 // non-visual objects
939 KeymapSwitcher {
940 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
941 }
942 BrightnessControl {}
943
944 Rectangle {
945 id: shutdownFadeOutRectangle
946 z: cursor.z + 1
947 enabled: false
948 visible: false
949 color: "black"
950 anchors.fill: parent
951 opacity: 0.0
952 NumberAnimation on opacity {
953 id: shutdownFadeOut
954 from: 0.0
955 to: 1.0
956 onStopped: {
957 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
958 DBusLomiriSessionService.shutdown();
959 }
960 }
961 }
962 }
963}