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 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
849
850 states: [
851 State {
852 name: "narrow"
853 when: overlay.width <= units.gu(60)
854 AnchorChanges {
855 target: notifications
856 anchors.left: parent.left
857 anchors.right: parent.right
858 }
859 },
860 State {
861 name: "wide"
862 when: overlay.width > units.gu(60)
863 AnchorChanges {
864 target: notifications
865 anchors.left: undefined
866 anchors.right: parent.right
867 }
868 PropertyChanges { target: notifications; width: units.gu(38) }
869 }
870 ]
871 }
872
873 EdgeBarrier {
874 id: rightEdgeBarrier
875 enabled: !greeter.shown
876
877 // NB: it does its own positioning according to the specified edge
878 edge: Qt.RightEdge
879
880 onPassed: {
881 panel.indicators.hide()
882 }
883
884 material: Component {
885 Item {
886 Rectangle {
887 width: parent.height
888 height: parent.width
889 rotation: 90
890 anchors.centerIn: parent
891 gradient: Gradient {
892 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
893 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
894 }
895 }
896 }
897 }
898 }
899
900 Item {
901 id: screenshotEditorContainer
902 anchors.fill: parent
903 z: itemGrabber.z - 2
904 visible: false
905
906 ScreenshotEditor {
907 id: screenshotEditor
908 anchors.fill: parent
909 anchors.topMargin: panel.panelHeight
910 enabled: !wizard.active
911
912 // Always hide the Launcher and Panel
913 onShown: {
914 console.log("Showing screenshot editor.");
915
916 launcher.hide();
917 panel.indicators.hide();
918 panel.applicationMenus.hide();
919
920 screenshotEditorContainer.visible = true;
921 }
922
923 onHidden: {
924 screenshotEditorContainer.visible = false;
925 }
926
927 // Make locking the screen abort the editing session, otherwise we
928 // would show the editor above the lockscreen.
929 Connections {
930 target: greeter
931 function onLockedChanged() {
932 if (!screenshotEditor.visible)
933 return;
934
935 if (greeter.locked)
936 screenshotEditor.hide()
937 }
938 }
939 }
940 }
941 }
942
943 Dialogs {
944 id: dialogs
945 objectName: "dialogs"
946 anchors.fill: parent
947 visible: hasActiveDialog
948 z: overlay.z + 10
949 usageScenario: shell.usageScenario
950 hasKeyboard: shell.hasKeyboard
951 onPowerOffClicked: {
952 shutdownFadeOutRectangle.enabled = true;
953 shutdownFadeOutRectangle.visible = true;
954 shutdownFadeOut.start();
955 }
956 }
957
958 Connections {
959 target: SessionBroadcast
960 function onShowHome() { if (shell.mode !== "greeter") showHome() }
961 }
962
963 URLDispatcher {
964 id: urlDispatcher
965 objectName: "urlDispatcher"
966 active: shell.mode === "greeter"
967 onUrlRequested: shell.activateURL(url)
968 }
969
970 ItemGrabber {
971 id: itemGrabber
972 anchors.fill: parent
973 z: dialogs.z + 10
974 enabled: shell.mode !== "greeter"
975 GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
976 Connections {
977 target: stage
978 ignoreUnknownSignals: true
979 function onItemSnapshotRequested(item) { itemGrabber.capture(item) }
980 }
981 onSnapshotTaken: {
982 if (!settings.enableScreenshotEditor)
983 return;
984
985 screenshotEditor.show(path)
986 }
987 }
988
989 Timer {
990 id: cursorHidingTimer
991 interval: 3000
992 running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
993 onTriggered: cursor.opacity = 0;
994 }
995
996 Cursor {
997 id: cursor
998 objectName: "cursor"
999
1000 z: itemGrabber.z + 1
1001 topBoundaryOffset: panel.panelHeight
1002 enabled: shell.hasMouse && screenWindow.active
1003 visible: enabled
1004
1005 property bool mouseNeverMoved: true
1006 Binding {
1007 target: cursor; property: "x"; value: shell.width / 2
1008 restoreMode: Binding.RestoreBinding
1009 when: cursor.mouseNeverMoved && cursor.visible
1010 }
1011 Binding {
1012 target: cursor; property: "y"; value: shell.height / 2
1013 restoreMode: Binding.RestoreBinding
1014 when: cursor.mouseNeverMoved && cursor.visible
1015 }
1016
1017 confiningItem: stage.itemConfiningMouseCursor
1018
1019 height: units.gu(3)
1020
1021 readonly property var previewRectangle: stage.previewRectangle.target &&
1022 stage.previewRectangle.target.dragging ?
1023 stage.previewRectangle : null
1024
1025 onPushedLeftBoundary: {
1026 if (buttons === Qt.NoButton) {
1027 launcher.pushEdge(amount);
1028 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
1029 previewRectangle.maximizeLeft(amount);
1030 }
1031 }
1032
1033 onPushedRightBoundary: {
1034 if (buttons === Qt.NoButton) {
1035 rightEdgeBarrier.push(amount);
1036 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
1037 previewRectangle.maximizeRight(amount);
1038 }
1039 }
1040
1041 onPushedTopBoundary: {
1042 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
1043 previewRectangle.maximize(amount);
1044 }
1045 }
1046 onPushedTopLeftCorner: {
1047 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1048 previewRectangle.maximizeTopLeft(amount);
1049 }
1050 }
1051 onPushedTopRightCorner: {
1052 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1053 previewRectangle.maximizeTopRight(amount);
1054 }
1055 }
1056 onPushedBottomLeftCorner: {
1057 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1058 previewRectangle.maximizeBottomLeft(amount);
1059 }
1060 }
1061 onPushedBottomRightCorner: {
1062 if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
1063 previewRectangle.maximizeBottomRight(amount);
1064 }
1065 }
1066 onPushStopped: {
1067 if (previewRectangle) {
1068 previewRectangle.stop();
1069 }
1070 }
1071
1072 onMouseMoved: {
1073 mouseNeverMoved = false;
1074 cursor.opacity = 1;
1075 }
1076
1077 Behavior on opacity { LomiriNumberAnimation {} }
1078 }
1079
1080 // non-visual objects
1081 KeymapSwitcher {
1082 focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
1083 }
1084 BrightnessControl {}
1085
1086 Rectangle {
1087 id: shutdownFadeOutRectangle
1088 z: cursor.z + 1
1089 enabled: false
1090 visible: false
1091 color: "black"
1092 anchors.fill: parent
1093 opacity: 0.0
1094 NumberAnimation on opacity {
1095 id: shutdownFadeOut
1096 from: 0.0
1097 to: 1.0
1098 onStopped: {
1099 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
1100 DBusLomiriSessionService.shutdown();
1101 }
1102 }
1103 }
1104 }
1105}