Lomiri
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 
18 import QtQuick 2.12
19 import QtQuick.Window 2.2
20 import AccountsService 0.1
21 import QtMir.Application 0.1
22 import Lomiri.Components 1.3
23 import Lomiri.Components.Popups 1.3
24 import Lomiri.Gestures 0.1
25 import Lomiri.Telephony 0.1 as Telephony
26 import Lomiri.ModemConnectivity 0.1
27 import Lomiri.Launcher 0.1
28 import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
29 import GSettings 1.0
30 import Utils 0.1
31 import Powerd 0.1
32 import SessionBroadcast 0.1
33 import "Greeter"
34 import "Launcher"
35 import "Panel"
36 import "Components"
37 import "Notifications"
38 import "Stage"
39 import "Tutorial"
40 import "Wizard"
41 import "Components/PanelState"
42 import Lomiri.Notifications 1.0 as NotificationBackend
43 import Lomiri.Session 0.1
44 import Lomiri.Indicators 0.1 as Indicators
45 import Cursor 1.1
46 import WindowManager 1.0
47 
48 
49 StyledItem {
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 }