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