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.8
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 || !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 (showingGreeter || 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  stage.closeSpread();
229  }
230 
231  function startLockedApp(app) {
232  topLevelSurfaceList.pendingActivation();
233 
234  if (greeter.locked) {
235  greeter.lockedApp = app;
236  }
237  startApp(app); // locked apps are always in our same session
238  }
239 
240  Binding {
241  target: LauncherModel
242  property: "applicationManager"
243  value: ApplicationManager
244  }
245 
246  Component.onCompleted: {
247  finishStartUpTimer.start();
248  }
249 
250  VolumeControl {
251  id: volumeControl
252  }
253 
254  PhysicalKeysMapper {
255  id: physicalKeysMapper
256  objectName: "physicalKeysMapper"
257 
258  onPowerKeyLongPressed: dialogs.showPowerDialog();
259  onVolumeDownTriggered: volumeControl.volumeDown();
260  onVolumeUpTriggered: volumeControl.volumeUp();
261  onScreenshotTriggered: itemGrabber.capture(shell);
262  }
263 
264  GlobalShortcut {
265  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
266  }
267 
268  WindowInputFilter {
269  id: inputFilter
270  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
271  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
272  }
273 
274  WindowInputMonitor {
275  objectName: "windowInputMonitor"
276  onHomeKeyActivated: {
277  // Ignore when greeter is active, to avoid pocket presses
278  if (!greeter.active) {
279  launcher.toggleDrawer(/* focusInputField */ false,
280  /* onlyOpen */ false,
281  /* alsoToggleLauncher */ true);
282  }
283  }
284  onTouchBegun: { cursor.opacity = 0; }
285  onTouchEnded: {
286  // move the (hidden) cursor to the last known touch position
287  var mappedCoords = mapFromItem(null, pos.x, pos.y);
288  cursor.x = mappedCoords.x;
289  cursor.y = mappedCoords.y;
290  cursor.mouseNeverMoved = false;
291  }
292  }
293 
294  AvailableDesktopArea {
295  id: availableDesktopAreaItem
296  anchors.fill: parent
297  anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
298  anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
299  }
300 
301  GSettings {
302  id: settings
303  schema.id: "com.lomiri.Shell"
304  }
305 
306  PanelState {
307  id: panelState
308  objectName: "panelState"
309  }
310 
311  Item {
312  id: stages
313  objectName: "stages"
314  width: parent.width
315  height: parent.height
316 
317  Stage {
318  id: stage
319  objectName: "stage"
320  anchors.fill: parent
321  focus: true
322 
323  dragAreaWidth: shell.edgeSize
324  background: wallpaperResolver.background
325  backgroundSourceSize: shell.largestScreenDimension
326 
327  applicationManager: ApplicationManager
328  topLevelSurfaceList: shell.topLevelSurfaceList
329  inputMethodRect: inputMethod.visibleRect
330  rightEdgePushProgress: rightEdgeBarrier.progress
331  availableDesktopArea: availableDesktopAreaItem
332  launcherLeftMargin: launcher.visibleWidth
333 
334  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
335  ? "phone"
336  : shell.usageScenario
337 
338  mode: usageScenario == "phone" ? "staged"
339  : usageScenario == "tablet" ? "stagedWithSideStage"
340  : "windowed"
341 
342  shellOrientation: shell.orientation
343  shellOrientationAngle: shell.orientationAngle
344  orientations: shell.orientations
345  nativeWidth: shell.nativeWidth
346  nativeHeight: shell.nativeHeight
347 
348  allowInteractivity: (!greeter || !greeter.shown)
349  && panel.indicators.fullyClosed
350  && !notifications.useModal
351  && !launcher.takesFocus
352 
353  suspended: greeter.shown
354  altTabPressed: physicalKeysMapper.altTabPressed
355  oskEnabled: shell.oskEnabled
356  spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
357  panelState: panelState
358 
359  onSpreadShownChanged: {
360  panel.indicators.hide();
361  panel.applicationMenus.hide();
362  }
363  }
364 
365  TouchGestureArea {
366  anchors.fill: stage
367 
368  minimumTouchPoints: 4
369  maximumTouchPoints: minimumTouchPoints
370 
371  readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
372  touchPoints.length >= minimumTouchPoints &&
373  touchPoints.length <= maximumTouchPoints
374  property bool wasPressed: false
375 
376  onRecognisedPressChanged: {
377  if (recognisedPress) {
378  wasPressed = true;
379  }
380  }
381 
382  onStatusChanged: {
383  if (status !== TouchGestureArea.Recognized) {
384  if (status === TouchGestureArea.WaitingForTouch) {
385  if (wasPressed && !dragging) {
386  launcher.toggleDrawer(true);
387  }
388  }
389  wasPressed = false;
390  }
391  }
392  }
393  }
394 
395  InputMethod {
396  id: inputMethod
397  objectName: "inputMethod"
398  anchors {
399  fill: parent
400  topMargin: panel.panelHeight
401  leftMargin: (launcher.lockedByUser && launcher.lockAllowed) ? launcher.panelWidth : 0
402  }
403  z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
404  }
405 
406  Loader {
407  id: greeterLoader
408  objectName: "greeterLoader"
409  anchors.fill: parent
410  sourceComponent: {
411  if (shell.mode != "shell") {
412  if (screenWindow.primary) return integratedGreeter;
413  return secondaryGreeter;
414  }
415  return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
416  }
417  onLoaded: {
418  item.objectName = "greeter"
419  }
420  property bool toggleDrawerAfterUnlock: false
421  Connections {
422  target: greeter
423  onActiveChanged: {
424  if (greeter.active)
425  return
426 
427  // Show drawer in case showHome() requests it
428  if (greeterLoader.toggleDrawerAfterUnlock) {
429  launcher.toggleDrawer(false);
430  greeterLoader.toggleDrawerAfterUnlock = false;
431  } else {
432  launcher.hide();
433  }
434  }
435  }
436  }
437 
438  Component {
439  id: integratedGreeter
440  Greeter {
441 
442  enabled: panel.indicators.fullyClosed // hides OSK when panel is open
443  hides: [launcher, panel.indicators, panel.applicationMenus]
444  tabletMode: shell.usageScenario != "phone"
445  forcedUnlock: wizard.active || shell.mode === "full-shell"
446  background: wallpaperResolver.background
447  backgroundSourceSize: shell.largestScreenDimension
448  hasCustomBackground: wallpaperResolver.hasCustomBackground
449  inputMethodRect: inputMethod.visibleRect
450  hasKeyboard: shell.hasKeyboard
451  allowFingerprint: !dialogs.hasActiveDialog &&
452  !notifications.topmostIsFullscreen &&
453  !panel.indicators.shown
454  panelHeight: panel.panelHeight
455 
456  // avoid overlapping with Launcher's edge drag area
457  // FIXME: Fix TouchRegistry & friends and remove this workaround
458  // Issue involves launcher's DDA getting disabled on a long
459  // left-edge drag
460  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
461 
462  onTease: {
463  if (!tutorial.running) {
464  launcher.tease();
465  }
466  }
467 
468  onEmergencyCall: startLockedApp("dialer-app")
469  }
470  }
471 
472  Component {
473  id: secondaryGreeter
474  SecondaryGreeter {
475  hides: [launcher, panel.indicators]
476  }
477  }
478 
479  Timer {
480  // See powerConnection for why this is useful
481  id: showGreeterDelayed
482  interval: 1
483  onTriggered: {
484  // Go through the dbus service, because it has checks for whether
485  // we are even allowed to lock or not.
486  DBusLomiriSessionService.PromptLock();
487  }
488  }
489 
490  Connections {
491  id: callConnection
492  target: callManager
493 
494  onHasCallsChanged: {
495  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
496  // We just received an incoming call while locked. The
497  // indicator will have already launched dialer-app for us, but
498  // there is a race between "hasCalls" changing and the dialer
499  // starting up. So in case we lose that race, we'll start/
500  // focus the dialer ourselves here too. Even if the indicator
501  // didn't launch the dialer for some reason (or maybe a call
502  // started via some other means), if an active call is
503  // happening, we want to be in the dialer.
504  startLockedApp("dialer-app")
505  }
506  }
507  }
508 
509  Connections {
510  id: powerConnection
511  target: Powerd
512 
513  onStatusChanged: {
514  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
515  !callManager.hasCalls && !wizard.active) {
516  // We don't want to simply call greeter.showNow() here, because
517  // that will take too long. Qt will delay button event
518  // handling until the greeter is done loading and may think the
519  // user held down the power button the whole time, leading to a
520  // power dialog being shown. Instead, delay showing the
521  // greeter until we've finished handling the event. We could
522  // make the greeter load asynchronously instead, but that
523  // introduces a whole host of timing issues, especially with
524  // its animations. So this is simpler.
525  showGreeterDelayed.start();
526  }
527  }
528  }
529 
530  function showHome() {
531  greeter.notifyUserRequestedApp();
532 
533  if (shell.mode === "greeter") {
534  SessionBroadcast.requestHomeShown(AccountsService.user);
535  } else {
536  if (!greeter.active) {
537  launcher.toggleDrawer(false);
538  } else {
539  greeterLoader.toggleDrawerAfterUnlock = true;
540  }
541  }
542  }
543 
544  Item {
545  id: overlay
546  z: 10
547 
548  anchors.fill: parent
549 
550  Panel {
551  id: panel
552  objectName: "panel"
553  anchors.fill: parent //because this draws indicator menus
554 
555  mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
556  minimizedPanelHeight: units.gu(3)
557  expandedPanelHeight: units.gu(7)
558  applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
559 
560  indicators {
561  hides: [launcher]
562  available: tutorial.panelEnabled
563  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
564  && (!greeter || !greeter.hasLockedApp)
565  && !shell.waitingOnGreeter
566  && settings.enableIndicatorMenu
567 
568  model: Indicators.IndicatorsModel {
569  id: indicatorsModel
570  // tablet and phone both use the same profile
571  // FIXME: use just "phone" for greeter too, but first fix
572  // greeter app launching to either load the app inside the
573  // greeter or tell the session to load the app. This will
574  // involve taking the url-dispatcher dbus name and using
575  // SessionBroadcast to tell the session.
576  profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
577  Component.onCompleted: {
578  load();
579  }
580  }
581  }
582 
583  applicationMenus {
584  hides: [launcher]
585  available: (!greeter || !greeter.shown)
586  && !shell.waitingOnGreeter
587  && !stage.spreadShown
588  }
589 
590  readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
591  ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
592  : false
593  fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
594  || greeter.hasLockedApp
595  greeterShown: greeter && greeter.shown
596  hasKeyboard: shell.hasKeyboard
597  panelState: panelState
598  supportsMultiColorLed: shell.supportsMultiColorLed
599  }
600 
601  Launcher {
602  id: launcher
603  objectName: "launcher"
604 
605  anchors.top: parent.top
606  anchors.topMargin: inverted ? 0 : panel.panelHeight
607  anchors.bottom: parent.bottom
608  width: parent.width
609  dragAreaWidth: shell.edgeSize
610  available: tutorial.launcherEnabled
611  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
612  && !greeter.hasLockedApp
613  && !shell.waitingOnGreeter
614  inverted: shell.usageScenario !== "desktop"
615  superPressed: physicalKeysMapper.superPressed
616  superTabPressed: physicalKeysMapper.superTabPressed
617  panelWidth: units.gu(settings.launcherWidth)
618  lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
619  topPanelHeight: panel.panelHeight
620  drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
621  privateMode: greeter.active
622  background: wallpaperResolver.background
623  backgroundSourceSize: shell.largestScreenDimension
624 
625  // It can be assumed that the Launcher and Panel would overlap if
626  // the Panel is open and taking up the full width of the shell
627  readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
628 
629  // The "autohideLauncher" setting is only valid in desktop mode
630  readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
631 
632  // The Launcher should absolutely not be locked visible under some
633  // conditions
634  readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
635 
636  onShowDashHome: showHome()
637  onLauncherApplicationSelected: {
638  greeter.notifyUserRequestedApp();
639  shell.activateApplication(appId);
640  }
641  onShownChanged: {
642  if (shown) {
643  panel.indicators.hide();
644  panel.applicationMenus.hide();
645  }
646  }
647  onDrawerShownChanged: {
648  if (drawerShown) {
649  panel.indicators.hide();
650  panel.applicationMenus.hide();
651  }
652  }
653  onFocusChanged: {
654  if (!focus) {
655  stage.focus = true;
656  }
657  }
658 
659  GlobalShortcut {
660  shortcut: Qt.MetaModifier | Qt.Key_A
661  onTriggered: {
662  launcher.toggleDrawer(true);
663  }
664  }
665  GlobalShortcut {
666  shortcut: Qt.AltModifier | Qt.Key_F1
667  onTriggered: {
668  launcher.openForKeyboardNavigation();
669  }
670  }
671  GlobalShortcut {
672  shortcut: Qt.MetaModifier | Qt.Key_0
673  onTriggered: {
674  if (LauncherModel.get(9)) {
675  activateApplication(LauncherModel.get(9).appId);
676  }
677  }
678  }
679  Repeater {
680  model: 9
681  GlobalShortcut {
682  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
683  onTriggered: {
684  if (LauncherModel.get(index)) {
685  activateApplication(LauncherModel.get(index).appId);
686  }
687  }
688  }
689  }
690  }
691 
692  KeyboardShortcutsOverlay {
693  objectName: "shortcutsOverlay"
694  enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
695  && height < parent.height - padding - panel.panelHeight
696  anchors.centerIn: parent
697  anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
698  anchors.verticalCenterOffset: panel.panelHeight/2
699  visible: opacity > 0
700  opacity: enabled ? 0.95 : 0
701 
702  Behavior on opacity {
703  LomiriNumberAnimation {}
704  }
705  }
706 
707  Tutorial {
708  id: tutorial
709  objectName: "tutorial"
710  anchors.fill: parent
711 
712  paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
713  || !hasTouchscreen // TODO #1661557 something better for no touchscreen
714  delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
715  inputMethod.visible ||
716  (launcher.shown && !launcher.lockedVisible) ||
717  panel.indicators.shown || stage.rightEdgeDragProgress > 0
718  usageScenario: shell.usageScenario
719  lastInputTimestamp: inputFilter.lastInputTimestamp
720  launcher: launcher
721  panel: panel
722  stage: stage
723  }
724 
725  Wizard {
726  id: wizard
727  objectName: "wizard"
728  anchors.fill: parent
729  deferred: shell.mode === "greeter"
730 
731  function unlockWhenDoneWithWizard() {
732  if (!active) {
733  ModemConnectivity.unlockAllModems();
734  }
735  }
736 
737  Component.onCompleted: unlockWhenDoneWithWizard()
738  onActiveChanged: unlockWhenDoneWithWizard()
739  }
740 
741  MouseArea { // modal notifications prevent interacting with other contents
742  anchors.fill: parent
743  visible: notifications.useModal
744  enabled: visible
745  }
746 
747  Notifications {
748  id: notifications
749 
750  model: NotificationBackend.Model
751  margin: units.gu(1)
752  hasMouse: shell.hasMouse
753  background: wallpaperResolver.background
754 
755  y: topmostIsFullscreen ? 0 : panel.panelHeight
756  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
757 
758  states: [
759  State {
760  name: "narrow"
761  when: overlay.width <= units.gu(60)
762  AnchorChanges {
763  target: notifications
764  anchors.left: parent.left
765  anchors.right: parent.right
766  }
767  },
768  State {
769  name: "wide"
770  when: overlay.width > units.gu(60)
771  AnchorChanges {
772  target: notifications
773  anchors.left: undefined
774  anchors.right: parent.right
775  }
776  PropertyChanges { target: notifications; width: units.gu(38) }
777  }
778  ]
779  }
780 
781  EdgeBarrier {
782  id: rightEdgeBarrier
783  enabled: !greeter.shown
784 
785  // NB: it does its own positioning according to the specified edge
786  edge: Qt.RightEdge
787 
788  onPassed: {
789  panel.indicators.hide()
790  }
791 
792  material: Component {
793  Item {
794  Rectangle {
795  width: parent.height
796  height: parent.width
797  rotation: 90
798  anchors.centerIn: parent
799  gradient: Gradient {
800  GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
801  GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
802  }
803  }
804  }
805  }
806  }
807  }
808 
809  Dialogs {
810  id: dialogs
811  objectName: "dialogs"
812  anchors.fill: parent
813  visible: hasActiveDialog
814  z: overlay.z + 10
815  usageScenario: shell.usageScenario
816  hasKeyboard: shell.hasKeyboard
817  onPowerOffClicked: {
818  shutdownFadeOutRectangle.enabled = true;
819  shutdownFadeOutRectangle.visible = true;
820  shutdownFadeOut.start();
821  }
822  }
823 
824  Connections {
825  target: SessionBroadcast
826  onShowHome: if (shell.mode !== "greeter") showHome()
827  }
828 
829  URLDispatcher {
830  id: urlDispatcher
831  objectName: "urlDispatcher"
832  active: shell.mode === "greeter"
833  onUrlRequested: shell.activateURL(url)
834  }
835 
836  ItemGrabber {
837  id: itemGrabber
838  anchors.fill: parent
839  z: dialogs.z + 10
840  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
841  Connections {
842  target: stage
843  ignoreUnknownSignals: true
844  onItemSnapshotRequested: itemGrabber.capture(item)
845  }
846  }
847 
848  Timer {
849  id: cursorHidingTimer
850  interval: 3000
851  running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
852  onTriggered: cursor.opacity = 0;
853  }
854 
855  Cursor {
856  id: cursor
857  objectName: "cursor"
858 
859  z: itemGrabber.z + 1
860  topBoundaryOffset: panel.panelHeight
861  enabled: shell.hasMouse && screenWindow.active
862  visible: enabled
863 
864  property bool mouseNeverMoved: true
865  Binding {
866  target: cursor; property: "x"; value: shell.width / 2
867  when: cursor.mouseNeverMoved && cursor.visible
868  }
869  Binding {
870  target: cursor; property: "y"; value: shell.height / 2
871  when: cursor.mouseNeverMoved && cursor.visible
872  }
873 
874  confiningItem: stage.itemConfiningMouseCursor
875 
876  height: units.gu(3)
877 
878  readonly property var previewRectangle: stage.previewRectangle.target &&
879  stage.previewRectangle.target.dragging ?
880  stage.previewRectangle : null
881 
882  onPushedLeftBoundary: {
883  if (buttons === Qt.NoButton) {
884  launcher.pushEdge(amount);
885  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
886  previewRectangle.maximizeLeft(amount);
887  }
888  }
889 
890  onPushedRightBoundary: {
891  if (buttons === Qt.NoButton) {
892  rightEdgeBarrier.push(amount);
893  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
894  previewRectangle.maximizeRight(amount);
895  }
896  }
897 
898  onPushedTopBoundary: {
899  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
900  previewRectangle.maximize(amount);
901  }
902  }
903  onPushedTopLeftCorner: {
904  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
905  previewRectangle.maximizeTopLeft(amount);
906  }
907  }
908  onPushedTopRightCorner: {
909  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
910  previewRectangle.maximizeTopRight(amount);
911  }
912  }
913  onPushedBottomLeftCorner: {
914  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
915  previewRectangle.maximizeBottomLeft(amount);
916  }
917  }
918  onPushedBottomRightCorner: {
919  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
920  previewRectangle.maximizeBottomRight(amount);
921  }
922  }
923  onPushStopped: {
924  if (previewRectangle) {
925  previewRectangle.stop();
926  }
927  }
928 
929  onMouseMoved: {
930  mouseNeverMoved = false;
931  cursor.opacity = 1;
932  }
933 
934  Behavior on opacity { LomiriNumberAnimation {} }
935  }
936 
937  // non-visual objects
938  KeymapSwitcher {
939  focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
940  }
941  BrightnessControl {}
942 
943  Rectangle {
944  id: shutdownFadeOutRectangle
945  z: cursor.z + 1
946  enabled: false
947  visible: false
948  color: "black"
949  anchors.fill: parent
950  opacity: 0.0
951  NumberAnimation on opacity {
952  id: shutdownFadeOut
953  from: 0.0
954  to: 1.0
955  onStopped: {
956  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
957  DBusLomiriSessionService.shutdown();
958  }
959  }
960  }
961  }
962 }