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