Lomiri
Loading...
Searching...
No Matches
Shell.qml
1/*
2 * Copyright (C) 2013-2016 Canonical Ltd.
3 * Copyright (C) 2019-2021 UBports Foundation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.12
19import QtQuick.Window 2.2
20import AccountsService 0.1
21import QtMir.Application 0.1
22import Lomiri.Components 1.3
23import Lomiri.Components.Popups 1.3
24import Lomiri.Gestures 0.1
25import Lomiri.Telephony 0.1 as Telephony
26import Lomiri.ModemConnectivity 0.1
27import Lomiri.Launcher 0.1
28import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
29import GSettings 1.0
30import Utils 0.1
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stage"
39import "Tutorial"
40import "Wizard"
41import "Components/PanelState"
42import Lomiri.Notifications 1.0 as NotificationBackend
43import Lomiri.Session 0.1
44import Lomiri.Indicators 0.1 as Indicators
45import Cursor 1.1
46import WindowManager 1.0
47
48
49StyledItem {
50 id: shell
51
52 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.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 (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 }
229
230 function startLockedApp(app) {
231 topLevelSurfaceList.pendingActivation();
232
233 if (greeter.locked) {
234 greeter.lockedApp = app;
235 }
236 startApp(app); // locked apps are always in our same session
237 }
238
239 Binding {
240 target: LauncherModel
241 property: "applicationManager"
242 value: ApplicationManager
243 }
244
245 Component.onCompleted: {
246 finishStartUpTimer.start();
247 }
248
249 VolumeControl {
250 id: volumeControl
251 }
252
253 PhysicalKeysMapper {
254 id: physicalKeysMapper
255 objectName: "physicalKeysMapper"
256
257 onPowerKeyLongPressed: dialogs.showPowerDialog();
258 onVolumeDownTriggered: volumeControl.volumeDown();
259 onVolumeUpTriggered: volumeControl.volumeUp();
260 onScreenshotTriggered: itemGrabber.capture(shell);
261 }
262
263 GlobalShortcut {
264 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
265 }
266
267 WindowInputFilter {
268 id: inputFilter
269 Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
270 Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
271 }
272
273 WindowInputMonitor {
274 objectName: "windowInputMonitor"
275 onHomeKeyActivated: {
276 // Ignore when greeter is active, to avoid pocket presses
277 if (!greeter.active) {
278 launcher.toggleDrawer(/* focusInputField */ false,
279 /* onlyOpen */ false,
280 /* alsoToggleLauncher */ true);
281 }
282 }
283 onTouchBegun: { cursor.opacity = 0; }
284 onTouchEnded: {
285 // move the (hidden) cursor to the last known touch position
286 var mappedCoords = mapFromItem(null, pos.x, pos.y);
287 cursor.x = mappedCoords.x;
288 cursor.y = mappedCoords.y;
289 cursor.mouseNeverMoved = false;
290 }
291 }
292
293 AvailableDesktopArea {
294 id: availableDesktopAreaItem
295 anchors.fill: parent
296 anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
297 anchors.leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
298 }
299
300 GSettings {
301 id: settings
302 schema.id: "com.lomiri.Shell"
303 }
304
305 PanelState {
306 id: panelState
307 objectName: "panelState"
308 }
309
310 Item {
311 id: stages
312 objectName: "stages"
313 width: parent.width
314 height: parent.height
315
316 Stage {
317 id: stage
318 objectName: "stage"
319 anchors.fill: parent
320 focus: true
321
322 dragAreaWidth: shell.edgeSize
323 background: wallpaperResolver.background
324 backgroundSourceSize: shell.largestScreenDimension
325
326 applicationManager: ApplicationManager
327 topLevelSurfaceList: shell.topLevelSurfaceList
328 inputMethodRect: inputMethod.visibleRect
329 rightEdgePushProgress: rightEdgeBarrier.progress
330 availableDesktopArea: availableDesktopAreaItem
331 launcherLeftMargin: launcher.visibleWidth
332
333 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
334 ? "phone"
335 : shell.usageScenario
336
337 mode: usageScenario == "phone" ? "staged"
338 : usageScenario == "tablet" ? "stagedWithSideStage"
339 : "windowed"
340
341 shellOrientation: shell.orientation
342 shellOrientationAngle: shell.orientationAngle
343 orientations: shell.orientations
344 nativeWidth: shell.nativeWidth
345 nativeHeight: shell.nativeHeight
346
347 allowInteractivity: (!greeter || !greeter.shown)
348 && panel.indicators.fullyClosed
349 && !notifications.useModal
350 && !launcher.takesFocus
351
352 suspended: greeter.shown
353 altTabPressed: physicalKeysMapper.altTabPressed
354 oskEnabled: shell.oskEnabled
355 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
356 panelState: panelState
357
358 onSpreadShownChanged: {
359 panel.indicators.hide();
360 panel.applicationMenus.hide();
361 }
362 }
363
364 TouchGestureArea {
365 anchors.fill: stage
366
367 minimumTouchPoints: 4
368 maximumTouchPoints: minimumTouchPoints
369
370 readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
371 touchPoints.length >= minimumTouchPoints &&
372 touchPoints.length <= maximumTouchPoints
373 property bool wasPressed: false
374
375 onRecognisedPressChanged: {
376 if (recognisedPress) {
377 wasPressed = true;
378 }
379 }
380
381 onStatusChanged: {
382 if (status !== TouchGestureArea.Recognized) {
383 if (status === TouchGestureArea.WaitingForTouch) {
384 if (wasPressed && !dragging) {
385 launcher.toggleDrawer(true);
386 }
387 }
388 wasPressed = false;
389 }
390 }
391 }
392 }
393
394 InputMethod {
395 id: inputMethod
396 objectName: "inputMethod"
397 anchors {
398 fill: parent
399 topMargin: panel.panelHeight
400 leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
401 }
402 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
403 }
404
405 Loader {
406 id: greeterLoader
407 objectName: "greeterLoader"
408 anchors.fill: parent
409 sourceComponent: {
410 if (shell.mode != "shell") {
411 if (screenWindow.primary) return integratedGreeter;
412 return secondaryGreeter;
413 }
414 return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
415 }
416 onLoaded: {
417 item.objectName = "greeter"
418 }
419 property bool toggleDrawerAfterUnlock: false
420 Connections {
421 target: greeter
422 onActiveChanged: {
423 if (greeter.active)
424 return
425
426 // Show drawer in case showHome() requests it
427 if (greeterLoader.toggleDrawerAfterUnlock) {
428 launcher.toggleDrawer(false);
429 greeterLoader.toggleDrawerAfterUnlock = false;
430 } else {
431 launcher.hide();
432 }
433 }
434 }
435 }
436
437 Component {
438 id: integratedGreeter
439 Greeter {
440
441 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
442 hides: [launcher, panel.indicators, panel.applicationMenus]
443 tabletMode: shell.usageScenario != "phone"
444 usageMode: shell.usageScenario
445 orientation: shell.orientation
446 forcedUnlock: wizard.active || shell.mode === "full-shell"
447 background: wallpaperResolver.background
448 backgroundSourceSize: shell.largestScreenDimension
449 hasCustomBackground: wallpaperResolver.hasCustomBackground
450 inputMethodRect: inputMethod.visibleRect
451 hasKeyboard: shell.hasKeyboard
452 allowFingerprint: !dialogs.hasActiveDialog &&
453 !notifications.topmostIsFullscreen &&
454 !panel.indicators.shown
455 panelHeight: panel.panelHeight
456
457 // avoid overlapping with Launcher's edge drag area
458 // FIXME: Fix TouchRegistry & friends and remove this workaround
459 // Issue involves launcher's DDA getting disabled on a long
460 // left-edge drag
461 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
462
463 onTease: {
464 if (!tutorial.running) {
465 launcher.tease();
466 }
467 }
468
469 onEmergencyCall: startLockedApp("dialer-app")
470 }
471 }
472
473 Component {
474 id: secondaryGreeter
475 SecondaryGreeter {
476 hides: [launcher, panel.indicators]
477 }
478 }
479
480 Timer {
481 // See powerConnection for why this is useful
482 id: showGreeterDelayed
483 interval: 1
484 onTriggered: {
485 // Go through the dbus service, because it has checks for whether
486 // we are even allowed to lock or not.
487 DBusLomiriSessionService.PromptLock();
488 }
489 }
490
491 Connections {
492 id: callConnection
493 target: callManager
494
495 onHasCallsChanged: {
496 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
497 // We just received an incoming call while locked. The
498 // indicator will have already launched dialer-app for us, but
499 // there is a race between "hasCalls" changing and the dialer
500 // starting up. So in case we lose that race, we'll start/
501 // focus the dialer ourselves here too. Even if the indicator
502 // didn't launch the dialer for some reason (or maybe a call
503 // started via some other means), if an active call is
504 // happening, we want to be in the dialer.
505 startLockedApp("dialer-app")
506 }
507 }
508 }
509
510 Connections {
511 id: powerConnection
512 target: Powerd
513
514 onStatusChanged: {
515 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
516 !callManager.hasCalls && !wizard.active) {
517 // We don't want to simply call greeter.showNow() here, because
518 // that will take too long. Qt will delay button event
519 // handling until the greeter is done loading and may think the
520 // user held down the power button the whole time, leading to a
521 // power dialog being shown. Instead, delay showing the
522 // greeter until we've finished handling the event. We could
523 // make the greeter load asynchronously instead, but that
524 // introduces a whole host of timing issues, especially with
525 // its animations. So this is simpler.
526 showGreeterDelayed.start();
527 }
528 }
529 }
530
531 function showHome() {
532 greeter.notifyUserRequestedApp();
533
534 if (shell.mode === "greeter") {
535 SessionBroadcast.requestHomeShown(AccountsService.user);
536 } else {
537 if (!greeter.active) {
538 launcher.toggleDrawer(false);
539 } else {
540 greeterLoader.toggleDrawerAfterUnlock = true;
541 }
542 }
543 }
544
545 Item {
546 id: overlay
547 z: 10
548
549 anchors.fill: parent
550
551 Panel {
552 id: panel
553 objectName: "panel"
554 anchors.fill: parent //because this draws indicator menus
555 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
556
557 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
558 minimizedPanelHeight: units.gu(3)
559 expandedPanelHeight: units.gu(7)
560 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
561
562 indicators {
563 hides: [launcher]
564 available: tutorial.panelEnabled
565 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
566 && (!greeter || !greeter.hasLockedApp)
567 && !shell.waitingOnGreeter
568 && settings.enableIndicatorMenu
569
570 model: Indicators.IndicatorsModel {
571 id: indicatorsModel
572 // tablet and phone both use the same profile
573 // FIXME: use just "phone" for greeter too, but first fix
574 // greeter app launching to either load the app inside the
575 // greeter or tell the session to load the app. This will
576 // involve taking the url-dispatcher dbus name and using
577 // SessionBroadcast to tell the session.
578 profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
579 Component.onCompleted: {
580 load();
581 }
582 }
583 }
584
585 applicationMenus {
586 hides: [launcher]
587 available: (!greeter || !greeter.shown)
588 && !shell.waitingOnGreeter
589 && !stage.spreadShown
590 }
591
592 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
593 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
594 : false
595 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
596 || greeter.hasLockedApp
597 greeterShown: greeter && greeter.shown
598 hasKeyboard: shell.hasKeyboard
599 panelState: panelState
600 supportsMultiColorLed: shell.supportsMultiColorLed
601 }
602
603 Launcher {
604 id: launcher
605 objectName: "launcher"
606
607 anchors.top: parent.top
608 anchors.topMargin: inverted ? 0 : panel.panelHeight
609 anchors.bottom: parent.bottom
610 width: parent.width
611 dragAreaWidth: shell.edgeSize
612 available: tutorial.launcherEnabled
613 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
614 && !greeter.hasLockedApp
615 && !shell.waitingOnGreeter
616 && shell.mode !== "greeter"
617 visible: shell.mode !== "greeter"
618 inverted: shell.usageScenario !== "desktop"
619 superPressed: physicalKeysMapper.superPressed
620 superTabPressed: physicalKeysMapper.superTabPressed
621 panelWidth: units.gu(settings.launcherWidth)
622 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
623 blurSource: settings.enableBlur ? (greeter.shown ? greeter : stages) : null
624 topPanelHeight: panel.panelHeight
625 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
626 privateMode: greeter.active
627 background: wallpaperResolver.background
628
629 // It can be assumed that the Launcher and Panel would overlap if
630 // the Panel is open and taking up the full width of the shell
631 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
632
633 // The "autohideLauncher" setting is only valid in desktop mode
634 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
635
636 // The Launcher should absolutely not be locked visible under some
637 // conditions
638 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
639
640 onShowDashHome: showHome()
641 onLauncherApplicationSelected: {
642 greeter.notifyUserRequestedApp();
643 shell.activateApplication(appId);
644 }
645 onShownChanged: {
646 if (shown) {
647 panel.indicators.hide();
648 panel.applicationMenus.hide();
649 }
650 }
651 onDrawerShownChanged: {
652 if (drawerShown) {
653 panel.indicators.hide();
654 panel.applicationMenus.hide();
655 }
656 }
657 onFocusChanged: {
658 if (!focus) {
659 stage.focus = true;
660 }
661 }
662
663 GlobalShortcut {
664 shortcut: Qt.MetaModifier | Qt.Key_A
665 onTriggered: {
666 launcher.toggleDrawer(true);
667 }
668 }
669 GlobalShortcut {
670 shortcut: Qt.AltModifier | Qt.Key_F1
671 onTriggered: {
672 launcher.openForKeyboardNavigation();
673 }
674 }
675 GlobalShortcut {
676 shortcut: Qt.MetaModifier | Qt.Key_0
677 onTriggered: {
678 if (LauncherModel.get(9)) {
679 activateApplication(LauncherModel.get(9).appId);
680 }
681 }
682 }
683 Repeater {
684 model: 9
685 GlobalShortcut {
686 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
687 onTriggered: {
688 if (LauncherModel.get(index)) {
689 activateApplication(LauncherModel.get(index).appId);
690 }
691 }
692 }
693 }
694 }
695
696 KeyboardShortcutsOverlay {
697 objectName: "shortcutsOverlay"
698 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
699 && height < parent.height - padding - panel.panelHeight
700 anchors.centerIn: parent
701 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
702 anchors.verticalCenterOffset: panel.panelHeight/2
703 visible: opacity > 0
704 opacity: enabled ? 0.95 : 0
705
706 Behavior on opacity {
707 LomiriNumberAnimation {}
708 }
709 }
710
711 Tutorial {
712 id: tutorial
713 objectName: "tutorial"
714 anchors.fill: parent
715
716 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
717 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
718 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
719 inputMethod.visible ||
720 (launcher.shown && !launcher.lockedVisible) ||
721 panel.indicators.shown || stage.rightEdgeDragProgress > 0
722 usageScenario: shell.usageScenario
723 lastInputTimestamp: inputFilter.lastInputTimestamp
724 launcher: launcher
725 panel: panel
726 stage: stage
727 }
728
729 Wizard {
730 id: wizard
731 objectName: "wizard"
732 anchors.fill: parent
733 deferred: shell.mode === "greeter"
734
735 function unlockWhenDoneWithWizard() {
736 if (!active) {
737 ModemConnectivity.unlockAllModems();
738 }
739 }
740
741 Component.onCompleted: unlockWhenDoneWithWizard()
742 onActiveChanged: unlockWhenDoneWithWizard()
743 }
744
745 MouseArea { // modal notifications prevent interacting with other contents
746 anchors.fill: parent
747 visible: notifications.useModal
748 enabled: visible
749 }
750
751 Notifications {
752 id: notifications
753
754 model: NotificationBackend.Model
755 margin: units.gu(1)
756 hasMouse: shell.hasMouse
757 background: wallpaperResolver.background
758 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
759
760 y: topmostIsFullscreen ? 0 : panel.panelHeight
761 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
762
763 states: [
764 State {
765 name: "narrow"
766 when: overlay.width <= units.gu(60)
767 AnchorChanges {
768 target: notifications
769 anchors.left: parent.left
770 anchors.right: parent.right
771 }
772 },
773 State {
774 name: "wide"
775 when: overlay.width > units.gu(60)
776 AnchorChanges {
777 target: notifications
778 anchors.left: undefined
779 anchors.right: parent.right
780 }
781 PropertyChanges { target: notifications; width: units.gu(38) }
782 }
783 ]
784 }
785
786 EdgeBarrier {
787 id: rightEdgeBarrier
788 enabled: !greeter.shown
789
790 // NB: it does its own positioning according to the specified edge
791 edge: Qt.RightEdge
792
793 onPassed: {
794 panel.indicators.hide()
795 }
796
797 material: Component {
798 Item {
799 Rectangle {
800 width: parent.height
801 height: parent.width
802 rotation: 90
803 anchors.centerIn: parent
804 gradient: Gradient {
805 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
806 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
807 }
808 }
809 }
810 }
811 }
812 }
813
814 Dialogs {
815 id: dialogs
816 objectName: "dialogs"
817 anchors.fill: parent
818 visible: hasActiveDialog
819 z: overlay.z + 10
820 usageScenario: shell.usageScenario
821 hasKeyboard: shell.hasKeyboard
822 onPowerOffClicked: {
823 shutdownFadeOutRectangle.enabled = true;
824 shutdownFadeOutRectangle.visible = true;
825 shutdownFadeOut.start();
826 }
827 }
828
829 Connections {
830 target: SessionBroadcast
831 onShowHome: if (shell.mode !== "greeter") showHome()
832 }
833
834 URLDispatcher {
835 id: urlDispatcher
836 objectName: "urlDispatcher"
837 active: shell.mode === "greeter"
838 onUrlRequested: shell.activateURL(url)
839 }
840
841 ItemGrabber {
842 id: itemGrabber
843 anchors.fill: parent
844 z: dialogs.z + 10
845 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
846 Connections {
847 target: stage
848 ignoreUnknownSignals: true
849 onItemSnapshotRequested: itemGrabber.capture(item)
850 }
851 }
852
853 Timer {
854 id: cursorHidingTimer
855 interval: 3000
856 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
857 onTriggered: cursor.opacity = 0;
858 }
859
860 Cursor {
861 id: cursor
862 objectName: "cursor"
863
864 z: itemGrabber.z + 1
865 topBoundaryOffset: panel.panelHeight
866 enabled: shell.hasMouse && screenWindow.active
867 visible: enabled
868
869 property bool mouseNeverMoved: true
870 Binding {
871 target: cursor; property: "x"; value: shell.width / 2
872 when: cursor.mouseNeverMoved && cursor.visible
873 }
874 Binding {
875 target: cursor; property: "y"; value: shell.height / 2
876 when: cursor.mouseNeverMoved && cursor.visible
877 }
878
879 confiningItem: stage.itemConfiningMouseCursor
880
881 height: units.gu(3)
882
883 readonly property var previewRectangle: stage.previewRectangle.target &&
884 stage.previewRectangle.target.dragging ?
885 stage.previewRectangle : null
886
887 onPushedLeftBoundary: {
888 if (buttons === Qt.NoButton) {
889 launcher.pushEdge(amount);
890 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
891 previewRectangle.maximizeLeft(amount);
892 }
893 }
894
895 onPushedRightBoundary: {
896 if (buttons === Qt.NoButton) {
897 rightEdgeBarrier.push(amount);
898 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
899 previewRectangle.maximizeRight(amount);
900 }
901 }
902
903 onPushedTopBoundary: {
904 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
905 previewRectangle.maximize(amount);
906 }
907 }
908 onPushedTopLeftCorner: {
909 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
910 previewRectangle.maximizeTopLeft(amount);
911 }
912 }
913 onPushedTopRightCorner: {
914 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915 previewRectangle.maximizeTopRight(amount);
916 }
917 }
918 onPushedBottomLeftCorner: {
919 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920 previewRectangle.maximizeBottomLeft(amount);
921 }
922 }
923 onPushedBottomRightCorner: {
924 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
925 previewRectangle.maximizeBottomRight(amount);
926 }
927 }
928 onPushStopped: {
929 if (previewRectangle) {
930 previewRectangle.stop();
931 }
932 }
933
934 onMouseMoved: {
935 mouseNeverMoved = false;
936 cursor.opacity = 1;
937 }
938
939 Behavior on opacity { LomiriNumberAnimation {} }
940 }
941
942 // non-visual objects
943 KeymapSwitcher {
944 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
945 }
946 BrightnessControl {}
947
948 Rectangle {
949 id: shutdownFadeOutRectangle
950 z: cursor.z + 1
951 enabled: false
952 visible: false
953 color: "black"
954 anchors.fill: parent
955 opacity: 0.0
956 NumberAnimation on opacity {
957 id: shutdownFadeOut
958 from: 0.0
959 to: 1.0
960 onStopped: {
961 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
962 DBusLomiriSessionService.shutdown();
963 }
964 }
965 }
966 }
967}