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.8
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
555 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
556 minimizedPanelHeight: units.gu(3)
557 expandedPanelHeight: units.gu(7)
558 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
559
560 indicators {
561 hides: [launcher]
562 available: tutorial.panelEnabled
563 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
564 && (!greeter || !greeter.hasLockedApp)
565 && !shell.waitingOnGreeter
566 && settings.enableIndicatorMenu
567
568 model: Indicators.IndicatorsModel {
569 id: indicatorsModel
570 // tablet and phone both use the same profile
571 // FIXME: use just "phone" for greeter too, but first fix
572 // greeter app launching to either load the app inside the
573 // greeter or tell the session to load the app. This will
574 // involve taking the url-dispatcher dbus name and using
575 // SessionBroadcast to tell the session.
576 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
577 Component.onCompleted: {
578 load();
579 }
580 }
581 }
582
583 applicationMenus {
584 hides: [launcher]
585 available: (!greeter || !greeter.shown)
586 && !shell.waitingOnGreeter
587 && !stage.spreadShown
588 }
589
590 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
591 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
592 : false
593 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
594 || greeter.hasLockedApp
595 greeterShown: greeter && greeter.shown
596 hasKeyboard: shell.hasKeyboard
597 panelState: panelState
598 supportsMultiColorLed: shell.supportsMultiColorLed
599 }
600
601 Launcher {
602 id: launcher
603 objectName: "launcher"
604
605 anchors.top: parent.top
606 anchors.topMargin: inverted ? 0 : panel.panelHeight
607 anchors.bottom: parent.bottom
608 width: parent.width
609 dragAreaWidth: shell.edgeSize
610 available: tutorial.launcherEnabled
611 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
612 && !greeter.hasLockedApp
613 && !shell.waitingOnGreeter
614 inverted: shell.usageScenario !== "desktop"
615 superPressed: physicalKeysMapper.superPressed
616 superTabPressed: physicalKeysMapper.superTabPressed
617 panelWidth: units.gu(settings.launcherWidth)
618 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
619 topPanelHeight: panel.panelHeight
620 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
621 privateMode: greeter.active
622 background: wallpaperResolver.background
623 backgroundSourceSize: shell.largestScreenDimension
624
625 // It can be assumed that the Launcher and Panel would overlap if
626 // the Panel is open and taking up the full width of the shell
627 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
628
629 // The "autohideLauncher" setting is only valid in desktop mode
630 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
631
632 // The Launcher should absolutely not be locked visible under some
633 // conditions
634 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
635
636 onShowDashHome: showHome()
637 onLauncherApplicationSelected: {
638 greeter.notifyUserRequestedApp();
639 shell.activateApplication(appId);
640 }
641 onShownChanged: {
642 if (shown) {
643 panel.indicators.hide();
644 panel.applicationMenus.hide();
645 }
646 }
647 onDrawerShownChanged: {
648 if (drawerShown) {
649 panel.indicators.hide();
650 panel.applicationMenus.hide();
651 }
652 }
653 onFocusChanged: {
654 if (!focus) {
655 stage.focus = true;
656 }
657 }
658
659 GlobalShortcut {
660 shortcut: Qt.MetaModifier | Qt.Key_A
661 onTriggered: {
662 launcher.toggleDrawer(true);
663 }
664 }
665 GlobalShortcut {
666 shortcut: Qt.AltModifier | Qt.Key_F1
667 onTriggered: {
668 launcher.openForKeyboardNavigation();
669 }
670 }
671 GlobalShortcut {
672 shortcut: Qt.MetaModifier | Qt.Key_0
673 onTriggered: {
674 if (LauncherModel.get(9)) {
675 activateApplication(LauncherModel.get(9).appId);
676 }
677 }
678 }
679 Repeater {
680 model: 9
681 GlobalShortcut {
682 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
683 onTriggered: {
684 if (LauncherModel.get(index)) {
685 activateApplication(LauncherModel.get(index).appId);
686 }
687 }
688 }
689 }
690 }
691
692 KeyboardShortcutsOverlay {
693 objectName: "shortcutsOverlay"
694 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
695 && height < parent.height - padding - panel.panelHeight
696 anchors.centerIn: parent
697 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
698 anchors.verticalCenterOffset: panel.panelHeight/2
699 visible: opacity > 0
700 opacity: enabled ? 0.95 : 0
701
702 Behavior on opacity {
703 LomiriNumberAnimation {}
704 }
705 }
706
707 Tutorial {
708 id: tutorial
709 objectName: "tutorial"
710 anchors.fill: parent
711
712 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
713 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
714 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
715 inputMethod.visible ||
716 (launcher.shown && !launcher.lockedVisible) ||
717 panel.indicators.shown || stage.rightEdgeDragProgress > 0
718 usageScenario: shell.usageScenario
719 lastInputTimestamp: inputFilter.lastInputTimestamp
720 launcher: launcher
721 panel: panel
722 stage: stage
723 }
724
725 Wizard {
726 id: wizard
727 objectName: "wizard"
728 anchors.fill: parent
729 deferred: shell.mode === "greeter"
730
731 function unlockWhenDoneWithWizard() {
732 if (!active) {
733 ModemConnectivity.unlockAllModems();
734 }
735 }
736
737 Component.onCompleted: unlockWhenDoneWithWizard()
738 onActiveChanged: unlockWhenDoneWithWizard()
739 }
740
741 MouseArea { // modal notifications prevent interacting with other contents
742 anchors.fill: parent
743 visible: notifications.useModal
744 enabled: visible
745 }
746
747 Notifications {
748 id: notifications
749
750 model: NotificationBackend.Model
751 margin: units.gu(1)
752 hasMouse: shell.hasMouse
753 background: wallpaperResolver.background
754
755 y: topmostIsFullscreen ? 0 : panel.panelHeight
756 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
757
758 states: [
759 State {
760 name: "narrow"
761 when: overlay.width <= units.gu(60)
762 AnchorChanges {
763 target: notifications
764 anchors.left: parent.left
765 anchors.right: parent.right
766 }
767 },
768 State {
769 name: "wide"
770 when: overlay.width > units.gu(60)
771 AnchorChanges {
772 target: notifications
773 anchors.left: undefined
774 anchors.right: parent.right
775 }
776 PropertyChanges { target: notifications; width: units.gu(38) }
777 }
778 ]
779 }
780
781 EdgeBarrier {
782 id: rightEdgeBarrier
783 enabled: !greeter.shown
784
785 // NB: it does its own positioning according to the specified edge
786 edge: Qt.RightEdge
787
788 onPassed: {
789 panel.indicators.hide()
790 }
791
792 material: Component {
793 Item {
794 Rectangle {
795 width: parent.height
796 height: parent.width
797 rotation: 90
798 anchors.centerIn: parent
799 gradient: Gradient {
800 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
801 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
802 }
803 }
804 }
805 }
806 }
807 }
808
809 Dialogs {
810 id: dialogs
811 objectName: "dialogs"
812 anchors.fill: parent
813 visible: hasActiveDialog
814 z: overlay.z + 10
815 usageScenario: shell.usageScenario
816 hasKeyboard: shell.hasKeyboard
817 onPowerOffClicked: {
818 shutdownFadeOutRectangle.enabled = true;
819 shutdownFadeOutRectangle.visible = true;
820 shutdownFadeOut.start();
821 }
822 }
823
824 Connections {
825 target: SessionBroadcast
826 onShowHome: if (shell.mode !== "greeter") showHome()
827 }
828
829 URLDispatcher {
830 id: urlDispatcher
831 objectName: "urlDispatcher"
832 active: shell.mode === "greeter"
833 onUrlRequested: shell.activateURL(url)
834 }
835
836 ItemGrabber {
837 id: itemGrabber
838 anchors.fill: parent
839 z: dialogs.z + 10
840 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
841 Connections {
842 target: stage
843 ignoreUnknownSignals: true
844 onItemSnapshotRequested: itemGrabber.capture(item)
845 }
846 }
847
848 Timer {
849 id: cursorHidingTimer
850 interval: 3000
851 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
852 onTriggered: cursor.opacity = 0;
853 }
854
855 Cursor {
856 id: cursor
857 objectName: "cursor"
858
859 z: itemGrabber.z + 1
860 topBoundaryOffset: panel.panelHeight
861 enabled: shell.hasMouse && screenWindow.active
862 visible: enabled
863
864 property bool mouseNeverMoved: true
865 Binding {
866 target: cursor; property: "x"; value: shell.width / 2
867 when: cursor.mouseNeverMoved && cursor.visible
868 }
869 Binding {
870 target: cursor; property: "y"; value: shell.height / 2
871 when: cursor.mouseNeverMoved && cursor.visible
872 }
873
874 confiningItem: stage.itemConfiningMouseCursor
875
876 height: units.gu(3)
877
878 readonly property var previewRectangle: stage.previewRectangle.target &&
879 stage.previewRectangle.target.dragging ?
880 stage.previewRectangle : null
881
882 onPushedLeftBoundary: {
883 if (buttons === Qt.NoButton) {
884 launcher.pushEdge(amount);
885 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
886 previewRectangle.maximizeLeft(amount);
887 }
888 }
889
890 onPushedRightBoundary: {
891 if (buttons === Qt.NoButton) {
892 rightEdgeBarrier.push(amount);
893 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
894 previewRectangle.maximizeRight(amount);
895 }
896 }
897
898 onPushedTopBoundary: {
899 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
900 previewRectangle.maximize(amount);
901 }
902 }
903 onPushedTopLeftCorner: {
904 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
905 previewRectangle.maximizeTopLeft(amount);
906 }
907 }
908 onPushedTopRightCorner: {
909 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
910 previewRectangle.maximizeTopRight(amount);
911 }
912 }
913 onPushedBottomLeftCorner: {
914 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915 previewRectangle.maximizeBottomLeft(amount);
916 }
917 }
918 onPushedBottomRightCorner: {
919 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920 previewRectangle.maximizeBottomRight(amount);
921 }
922 }
923 onPushStopped: {
924 if (previewRectangle) {
925 previewRectangle.stop();
926 }
927 }
928
929 onMouseMoved: {
930 mouseNeverMoved = false;
931 cursor.opacity = 1;
932 }
933
934 Behavior on opacity { LomiriNumberAnimation {} }
935 }
936
937 // non-visual objects
938 KeymapSwitcher {
939 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
940 }
941 BrightnessControl {}
942
943 Rectangle {
944 id: shutdownFadeOutRectangle
945 z: cursor.z + 1
946 enabled: false
947 visible: false
948 color: "black"
949 anchors.fill: parent
950 opacity: 0.0
951 NumberAnimation on opacity {
952 id: shutdownFadeOut
953 from: 0.0
954 to: 1.0
955 onStopped: {
956 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
957 DBusLomiriSessionService.shutdown();
958 }
959 }
960 }
961 }
962}