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