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.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 (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 }
524 }
525
526 Timer {
527 // See powerConnection for why this is useful
528 id: showGreeterDelayed
529 interval: 1
530 onTriggered: {
531 // Go through the dbus service, because it has checks for whether
532 // we are even allowed to lock or not.
533 DBusLomiriSessionService.PromptLock();
534 }
535 }
536
537 Connections {
538 id: callConnection
539 target: callManager
540
541 function onHasCallsChanged() {
542 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "lomiri-dialer-app") {
543 // We just received an incoming call while locked. The
544 // indicator will have already launched lomiri-dialer-app for
545 // us, but there is a race between "hasCalls" changing and the
546 // dialer starting up. So in case we lose that race, we'll
547 // start/focus the dialer ourselves here too. Even if the
548 // indicator didn't launch the dialer for some reason (or maybe
549 // a call started via some other means), if an active call is
550 // happening, we want to be in the dialer.
551 startLockedApp("lomiri-dialer-app")
552 }
553 }
554 }
555
556 Connections {
557 id: powerConnection
558 target: Powerd
559
560 function onStatusChanged(reason) {
561 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
562 !callManager.hasCalls && !wizard.active) {
563 // We don't want to simply call greeter.showNow() here, because
564 // that will take too long. Qt will delay button event
565 // handling until the greeter is done loading and may think the
566 // user held down the power button the whole time, leading to a
567 // power dialog being shown. Instead, delay showing the
568 // greeter until we've finished handling the event. We could
569 // make the greeter load asynchronously instead, but that
570 // introduces a whole host of timing issues, especially with
571 // its animations. So this is simpler.
572 showGreeterDelayed.start();
573 }
574 }
575 }
576
577 function showHome() {
578 greeter.notifyUserRequestedApp();
579
580 if (shell.mode === "greeter") {
581 SessionBroadcast.requestHomeShown(AccountsService.user);
582 } else {
583 if (!greeter.active) {
584 launcher.toggleDrawer(false);
585 } else {
586 greeterLoader.toggleDrawerAfterUnlock = true;
587 }
588 }
589 }
590
591 Item {
592 id: overlay
593 z: 10
594
595 anchors.fill: parent
596
597 SwipeArea {
598 objectName: "fullscreenSwipeDown"
599 enabled: panel.state === "offscreen"
600 direction: SwipeArea.Downwards
601 immediateRecognition: false
602 height: units.gu(2)
603 anchors {
604 top: parent.top
605 left: parent.left
606 right: parent.right
607 }
608 onDraggingChanged: {
609 if (dragging) {
610 panel.temporarilyShow()
611 }
612 }
613 }
614
615 // TODO: remove when we no longer have With/WithoutCutouts split, this just prevents warnings
616 // about assigning to a property that doesn't exist if the WithoutCutouts variant is loaded
617 // FIXME: use attached properties instead of huge blocks of passthrough properties
618 Binding {
619 target: panel
620 property: "orientation"
621 value: shell.orientation
622 when: panel.hasOwnProperty("orientation")
623 }
624
625 Panel {
626 id: panel
627 objectName: "panel"
628 anchors.fill: parent //because this draws indicator menus
629 blurSource: shell.blurSource
630 z: screenshotEditor.visible ? screenshotEditorContainer.z + 1 : 0
631 mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
632 minimizedPanelHeight: shell.screenIndex === 0
633 && shell.orientation === Qt.PortraitOrientation
634 && deviceConfig.collapsedPanelHeight
635 || units.gu(3)
636 expandedPanelHeight: units.gu(7)
637 applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
638 screenIndex: shell.screenIndex
639
640 indicators {
641 hides: [launcher]
642 available: tutorial.panelEnabled
643 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
644 && (!greeter || !greeter.hasLockedApp)
645 && !shell.waitingOnGreeter
646 && settings.enableIndicatorMenu
647 && !screenshotEditor.visible
648
649 model: Indicators.IndicatorsModel {
650 id: indicatorsModel
651 // tablet and phone both use the same profile
652 // FIXME: use just "phone" for greeter too, but first fix
653 // greeter app launching to either load the app inside the
654 // greeter or tell the session to load the app. This will
655 // involve taking the url-dispatcher dbus name and using
656 // SessionBroadcast to tell the session.
657 // For now indicators will just hide their buttons that
658 // usually spawn lomiri-system-settings, based on the
659 // active username being 'lightdm'.
660 profile: shell.mode === "greeter"
661 ? ((shell.usageScenario === "phone" || shell.usageScenario === "tablet")
662 ? "phone_greeter" : "desktop_greeter")
663 : "phone"
664 Component.onCompleted: {
665 load();
666 }
667 }
668 }
669
670 applicationMenus {
671 hides: [launcher]
672 available: (!greeter || !greeter.shown)
673 && !shell.waitingOnGreeter
674 && !stage.spreadShown
675 && !screenshotEditor.visible
676 }
677
678 readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
679 ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
680 : false
681 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
682 || greeter.hasLockedApp
683 greeterShown: greeter && greeter.shown
684 hasKeyboard: shell.hasKeyboard
685 panelState: panelState
686 supportsMultiColorLed: shell.supportsMultiColorLed
687 }
688
689 Launcher {
690 id: launcher
691 objectName: "launcher"
692
693 anchors.top: parent.top
694 anchors.topMargin: panel.panelHeight
695 anchors.bottom: parent.bottom
696 width: parent.width
697 dragAreaWidth: shell.edgeSize
698 available: tutorial.launcherEnabled
699 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
700 && !greeter.hasLockedApp
701 && !shell.waitingOnGreeter
702 && shell.mode !== "greeter"
703 && !screenshotEditor.visible
704 visible: shell.mode !== "greeter"
705 inverted: shell.usageScenario !== "desktop"
706 superPressed: physicalKeysMapper.superPressed
707 superTabPressed: physicalKeysMapper.superTabPressed
708 panelWidth: units.gu(settings.launcherWidth)
709 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
710 blurSource: shell.blurSource
711 topPanelHeight: panel.panelHeight
712 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
713 privateMode: greeter.active
714 background: wallpaperResolver.resolvedImage
715
716 // It can be assumed that the Launcher and Panel would overlap if
717 // the Panel is open and taking up the full width of the shell
718 readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
719
720 // The "autohideLauncher" setting is only valid in desktop mode
721 readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
722
723 // The Launcher should absolutely not be locked visible under some
724 // conditions
725 readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
726
727 onShowDashHome: showHome()
728 onLauncherApplicationSelected: {
729 greeter.notifyUserRequestedApp();
730 shell.activateApplication(appId);
731 }
732 onShownChanged: {
733 if (shown) {
734 panel.indicators.hide();
735 panel.applicationMenus.hide();
736 }
737 }
738 onDrawerShownChanged: {
739 if (drawerShown) {
740 panel.indicators.hide();
741 panel.applicationMenus.hide();
742 }
743 }
744 onFocusChanged: {
745 if (!focus) {
746 stage.focus = true;
747 }
748 }
749
750 GlobalShortcut {
751 shortcut: Qt.MetaModifier | Qt.Key_A
752 onTriggered: {
753 launcher.toggleDrawer(true);
754 }
755 }
756 GlobalShortcut {
757 shortcut: Qt.AltModifier | Qt.Key_F1
758 onTriggered: {
759 launcher.openForKeyboardNavigation();
760 }
761 }
762 GlobalShortcut {
763 shortcut: Qt.MetaModifier | Qt.Key_0
764 onTriggered: {
765 if (LauncherModel.get(9)) {
766 activateApplication(LauncherModel.get(9).appId);
767 }
768 }
769 }
770 Repeater {
771 model: 9
772 GlobalShortcut {
773 shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
774 onTriggered: {
775 if (LauncherModel.get(index)) {
776 activateApplication(LauncherModel.get(index).appId);
777 }
778 }
779 }
780 }
781 }
782
783 KeyboardShortcutsOverlay {
784 objectName: "shortcutsOverlay"
785 enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
786 && height < parent.height - padding - panel.panelHeight
787 anchors.centerIn: parent
788 anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
789 anchors.verticalCenterOffset: panel.panelHeight/2
790 visible: opacity > 0
791 opacity: enabled ? 0.95 : 0
792
793 Behavior on opacity {
794 LomiriNumberAnimation {}
795 }
796 }
797
798 Tutorial {
799 id: tutorial
800 objectName: "tutorial"
801 anchors.fill: parent
802
803 paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
804 || !hasTouchscreen // TODO #1661557 something better for no touchscreen
805 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
806 inputMethod.visible ||
807 (launcher.shown && !launcher.lockedVisible) ||
808 panel.indicators.shown || stage.rightEdgeDragProgress > 0
809 usageScenario: shell.usageScenario
810 lastInputTimestamp: inputFilter.lastInputTimestamp
811 launcher: launcher
812 panel: panel
813 stage: stage
814 }
815
816 Wizard {
817 id: wizard
818 objectName: "wizard"
819 anchors.fill: parent
820 deferred: shell.mode === "greeter"
821
822 function unlockWhenDoneWithWizard() {
823 if (!active && shell.mode !== "greeter") {
824 ModemConnectivity.unlockAllModems();
825 }
826 }
827
828 Component.onCompleted: unlockWhenDoneWithWizard()
829 onActiveChanged: unlockWhenDoneWithWizard()
830 }
831
832 MouseArea { // modal notifications prevent interacting with other contents
833 anchors.fill: parent
834 visible: notifications.useModal
835 enabled: visible
836 }
837
838 Notifications {
839 id: notifications
840
841 model: NotificationBackend.Model
842 margin: units.gu(1)
843 hasMouse: shell.hasMouse
844 background: wallpaperResolver.resolvedImage
845 privacyMode: greeter.locked && AccountsService.hideNotificationContentWhileLocked
846
847 y: topmostIsFullscreen ? 0 : panel.panelHeight
848 z: screenshotEditor.visible ? screenshotEditorContainer.z + 1 : 0
849 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
850
851 states: [
852 State {
853 name: "narrow"
854 when: overlay.width <= units.gu(60)
855 AnchorChanges {
856 target: notifications
857 anchors.left: parent.left
858 anchors.right: parent.right
859 }
860 },
861 State {
862 name: "wide"
863 when: overlay.width > units.gu(60)
864 AnchorChanges {
865 target: notifications
866 anchors.left: undefined
867 anchors.right: parent.right
868 }
869 PropertyChanges { target: notifications; width: units.gu(38) }
870 }
871 ]
872 }
873
874 EdgeBarrier {
875 id: rightEdgeBarrier
876 enabled: !greeter.shown
877
878 // NB: it does its own positioning according to the specified edge
879 edge: Qt.RightEdge
880
881 onPassed: {
882 panel.indicators.hide()
883 }
884
885 material: Component {
886 Item {
887 Rectangle {
888 width: parent.height
889 height: parent.width
890 rotation: 90
891 anchors.centerIn: parent
892 gradient: Gradient {
893 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
894 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
895 }
896 }
897 }
898 }
899 }
900
901 Item {
902 id: screenshotEditorContainer
903 anchors.fill: parent
904 z: itemGrabber.z - 2
905 visible: false
906
907 ScreenshotEditor {
908 id: screenshotEditor
909 anchors.fill: parent
910 anchors.topMargin: panel.panelHeight
911 enabled: !wizard.active
912
913 // Always hide the Launcher and Panel
914 onShown: {
915 console.log("Showing screenshot editor.");
916
917 launcher.hide();
918 panel.indicators.hide();
919 panel.applicationMenus.hide();
920
921 screenshotEditorContainer.visible = true;
922 }
923
924 onHidden: {
925 screenshotEditorContainer.visible = false;
926 }
927
928 // Make locking the screen abort the editing session, otherwise we
929 // would show the editor above the lockscreen.
930 Connections {
931 target: greeter
932 function onLockedChanged() {
933 if (!screenshotEditor.visible)
934 return;
935
936 if (greeter.locked)
937 screenshotEditor.hide()
938 }
939 }
940 }
941 }
942 }
943
944 Dialogs {
945 id: dialogs
946 objectName: "dialogs"
947 anchors.fill: parent
948 visible: hasActiveDialog
949 z: overlay.z + 10
950 usageScenario: shell.usageScenario
951 hasKeyboard: shell.hasKeyboard
952 onPowerOffClicked: {
953 shutdownFadeOutRectangle.enabled = true;
954 shutdownFadeOutRectangle.visible = true;
955 shutdownFadeOut.start();
956 }
957 }
958
959 Connections {
960 target: SessionBroadcast
961 function onShowHome() { if (shell.mode !== "greeter") showHome() }
962 }
963
964 URLDispatcher {
965 id: urlDispatcher
966 objectName: "urlDispatcher"
967 active: shell.mode === "greeter"
968 onUrlRequested: shell.activateURL(url)
969 }
970
971 ItemGrabber {
972 id: itemGrabber
973 anchors.fill: parent
974 z: dialogs.z + 10
975 enabled: shell.mode !== "greeter"
976 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
977 Connections {
978 target: stage
979 ignoreUnknownSignals: true
980 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
981 }
982 onSnapshotTaken: {
983 if (!settings.enableScreenshotEditor)
984 return;
985
986 screenshotEditor.show(path)
987 }
988 }
989
990 Timer {
991 id: cursorHidingTimer
992 interval: 3000
993 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
994 onTriggered: cursor.opacity = 0;
995 }
996
997 Cursor {
998 id: cursor
999 objectName: "cursor"
1000
1001 z: itemGrabber.z + 1
1002 topBoundaryOffset: panel.panelHeight
1003 enabled: shell.hasMouse && screenWindow.active
1004 visible: enabled
1005
1006 property bool mouseNeverMoved: true
1007 Binding {
1008 target: cursor; property: "x"; value: shell.width / 2
1009 restoreMode: Binding.RestoreBinding
1010 when: cursor.mouseNeverMoved && cursor.visible
1011 }
1012 Binding {
1013 target: cursor; property: "y"; value: shell.height / 2
1014 restoreMode: Binding.RestoreBinding
1015 when: cursor.mouseNeverMoved && cursor.visible
1016 }
1017
1018 confiningItem: stage.itemConfiningMouseCursor
1019
1020 height: units.gu(3)
1021
1022 readonly property var previewRectangle: stage.previewRectangle.target &&
1023 stage.previewRectangle.target.dragging ?
1024 stage.previewRectangle : null
1025
1026 onPushedLeftBoundary: {
1027 if (buttons === Qt.NoButton) {
1028 launcher.pushEdge(amount);
1029 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
1030 previewRectangle.maximizeLeft(amount);
1031 }
1032 }
1033
1034 onPushedRightBoundary: {
1035 if (buttons === Qt.NoButton) {
1036 rightEdgeBarrier.push(amount);
1037 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
1038 previewRectangle.maximizeRight(amount);
1039 }
1040 }
1041
1042 onPushedTopBoundary: {
1043 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
1044 previewRectangle.maximize(amount);
1045 }
1046 }
1047 onPushedTopLeftCorner: {
1048 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1049 previewRectangle.maximizeTopLeft(amount);
1050 }
1051 }
1052 onPushedTopRightCorner: {
1053 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1054 previewRectangle.maximizeTopRight(amount);
1055 }
1056 }
1057 onPushedBottomLeftCorner: {
1058 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1059 previewRectangle.maximizeBottomLeft(amount);
1060 }
1061 }
1062 onPushedBottomRightCorner: {
1063 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1064 previewRectangle.maximizeBottomRight(amount);
1065 }
1066 }
1067 onPushStopped: {
1068 if (previewRectangle) {
1069 previewRectangle.stop();
1070 }
1071 }
1072
1073 onMouseMoved: {
1074 mouseNeverMoved = false;
1075 cursor.opacity = 1;
1076 }
1077
1078 Behavior on opacity { LomiriNumberAnimation {} }
1079 }
1080
1081 // non-visual objects
1082 KeymapSwitcher {
1083 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
1084 }
1085 BrightnessControl {}
1086
1087 Rectangle {
1088 id: shutdownFadeOutRectangle
1089 z: cursor.z + 1
1090 enabled: false
1091 visible: false
1092 color: "black"
1093 anchors.fill: parent
1094 opacity: 0.0
1095 NumberAnimation on opacity {
1096 id: shutdownFadeOut
1097 from: 0.0
1098 to: 1.0
1099 onStopped: {
1100 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
1101 DBusLomiriSessionService.shutdown();
1102 }
1103 }
1104 }
1105 }
1106}