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