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