Unity 8
Shell.qml
1 /*
2  * Copyright (C) 2013-2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import QtQuick.Window 2.2
19 import AccountsService 0.1
20 import Unity.Application 0.1
21 import Ubuntu.Components 1.3
22 import Ubuntu.Components.Popups 1.3
23 import Ubuntu.Gestures 0.1
24 import Ubuntu.Telephony 0.1 as Telephony
25 import Unity.Connectivity 0.1
26 import Unity.Launcher 0.1
27 import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28 import GSettings 1.0
29 import Utils 0.1
30 import Powerd 0.1
31 import SessionBroadcast 0.1
32 import "Greeter"
33 import "Launcher"
34 import "Panel"
35 import "Components"
36 import "Notifications"
37 import "Stages"
38 import "Tutorial"
39 import "Wizard"
40 import Unity.Notifications 1.0 as NotificationBackend
41 import Unity.Session 0.1
42 import Unity.DashCommunicator 0.1
43 import Unity.Indicators 0.1 as Indicators
44 import Cursor 1.1
45 import WindowManager 0.1
46 
47 
48 StyledItem {
49  id: shell
50 
51  theme.name: "Ubuntu.Components.Themes.SuruDark"
52 
53  // to be set from outside
54  property int orientationAngle: 0
55  property int orientation
56  property Orientations orientations
57  property real nativeWidth
58  property real nativeHeight
59  property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
60  property bool beingResized
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  applicationsDisplayLoader.item.updateFocusedAppOrientation();
66  }
67  function updateFocusedAppOrientationAnimated() {
68  applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
69  }
70  property bool hasMouse: false
71 
72  // to be read from outside
73  readonly property int mainAppWindowOrientationAngle:
74  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
75 
76  readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
77  && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
78  && (!greeter || !greeter.animating)
79 
80  readonly property bool showingGreeter: greeter && greeter.shown
81 
82  property bool startingUp: true
83  Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
84 
85  property int supportedOrientations: {
86  if (startingUp) {
87  // Ensure we don't rotate during start up
88  return Qt.PrimaryOrientation;
89  } else if (greeter && greeter.shown) {
90  return Qt.PrimaryOrientation;
91  } else if (applicationsDisplayLoader.item) {
92  return shell.orientations.map(applicationsDisplayLoader.item.supportedOrientations);
93  } else {
94  // we just don't care
95  return Qt.PortraitOrientation
96  | Qt.LandscapeOrientation
97  | Qt.InvertedPortraitOrientation
98  | Qt.InvertedLandscapeOrientation;
99  }
100  }
101 
102  readonly property var mainApp:
103  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
104  onMainAppChanged: {
105  if (mainApp) {
106  _onMainAppChanged(mainApp.appId);
107  }
108  }
109  Connections {
110  target: ApplicationManager
111  onFocusRequested: {
112  if (shell.mainApp && shell.mainApp.appId === appId) {
113  _onMainAppChanged(appId);
114  }
115  }
116  }
117  function _onMainAppChanged(appId) {
118  if (wizard.active && appId != "" && appId != "unity8-dash") {
119  // If this happens on first boot, we may be in the
120  // wizard while receiving a call. But a call is more
121  // important than the wizard so just bail out of it.
122  wizard.hide();
123  }
124 
125  if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
126  // If we are in the middle of a call, make dialer lockedApp and show it.
127  // This can happen if user backs out of dialer back to greeter, then
128  // launches dialer again.
129  greeter.lockedApp = appId;
130  }
131  greeter.notifyAppFocusRequested(appId);
132 
133  panel.indicators.hide();
134  launcher.hide();
135  }
136 
137  // For autopilot consumption
138  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
139 
140  // Note when greeter is waiting on PAM, so that we can disable edges until
141  // we know which user data to show and whether the session is locked.
142  readonly property bool waitingOnGreeter: greeter && greeter.waiting
143 
144  property real edgeSize: units.gu(settings.edgeDragWidth)
145 
146  WallpaperResolver {
147  id: wallpaperResolver
148  width: shell.width
149  }
150 
151  readonly property alias greeter: greeterLoader.item
152 
153  function activateApplication(appId) {
154  if (ApplicationManager.findApplication(appId)) {
155  ApplicationManager.requestFocusApplication(appId);
156  } else {
157  ApplicationManager.startApplication(appId);
158  }
159  }
160 
161  function startLockedApp(app) {
162  if (greeter.locked) {
163  greeter.lockedApp = app;
164  }
165  shell.activateApplication(app);
166  }
167 
168  Binding {
169  target: LauncherModel
170  property: "applicationManager"
171  value: ApplicationManager
172  }
173 
174  Component.onCompleted: {
175  finishStartUpTimer.start();
176  }
177 
178  VolumeControl {
179  id: volumeControl
180  indicators: panel.indicators
181  }
182 
183  DashCommunicator {
184  id: dash
185  objectName: "dashCommunicator"
186  }
187 
188  PhysicalKeysMapper {
189  id: physicalKeysMapper
190  objectName: "physicalKeysMapper"
191 
192  onPowerKeyLongPressed: dialogs.showPowerDialog();
193  onVolumeDownTriggered: volumeControl.volumeDown();
194  onVolumeUpTriggered: volumeControl.volumeUp();
195  onScreenshotTriggered: itemGrabber.capture(shell);
196  }
197 
198  GlobalShortcut {
199  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
200  }
201 
202  WindowInputFilter {
203  id: inputFilter
204  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
205  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
206  }
207 
208  WindowInputMonitor {
209  objectName: "windowInputMonitor"
210  onHomeKeyActivated: {
211  // Ignore when greeter is active, to avoid pocket presses
212  if (!greeter.active) {
213  launcher.fadeOut();
214  shell.showHome();
215  }
216  }
217  onTouchBegun: { cursor.opacity = 0; }
218  onTouchEnded: {
219  // move the (hidden) cursor to the last known touch position
220  var mappedCoords = mapFromItem(null, pos.x, pos.y);
221  cursor.x = mappedCoords.x;
222  cursor.y = mappedCoords.y;
223  cursor.mouseNeverMoved = false;
224  }
225  }
226 
227  GSettings {
228  id: settings
229  schema.id: "com.canonical.Unity8"
230  }
231 
232  Item {
233  id: stages
234  objectName: "stages"
235  width: parent.width
236  height: parent.height
237 
238  TopLevelSurfaceList {
239  id: topLevelSurfaceList
240  objectName: "topLevelSurfaceList"
241  applicationsModel: ApplicationManager
242  }
243 
244  Loader {
245  id: applicationsDisplayLoader
246  objectName: "applicationsDisplayLoader"
247  anchors.fill: parent
248 
249  // When we have a locked app, we only want to show that one app.
250  // FIXME: do this in a less traumatic way. We currently only allow
251  // locked apps in phone mode (see FIXME in Lockscreen component in
252  // this same file). When that changes, we need to do something
253  // nicer here. But this code is currently just to prevent a
254  // theoretical attack where user enters lockedApp mode, then makes
255  // the screen larger (maybe connects to monitor) and tries to enter
256  // tablet mode.
257 
258  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
259  ? "phone"
260  : shell.usageScenario
261  readonly property string qmlComponent: {
262  if (shell.mode === "greeter") {
263  return "Stages/AbstractStage.qml"
264  } else if (applicationsDisplayLoader.usageScenario === "phone") {
265  return "Stages/PhoneStage.qml";
266  } else if (applicationsDisplayLoader.usageScenario === "tablet") {
267  return "Stages/TabletStage.qml";
268  } else {
269  return "Stages/DesktopStage.qml";
270  }
271  }
272  // TODO: Ensure the current stage is destroyed before the new one gets loaded.
273  // Currently the new one will get loaded while the old is still hanging
274  // around for a bit, which might lead to conflicts where both stages
275  // change the model simultaneously.
276  onQmlComponentChanged: {
277  if (item) item.stageAboutToBeUnloaded();
278  source = qmlComponent;
279  }
280 
281  property bool interactive: (!greeter || !greeter.shown)
282  && panel.indicators.fullyClosed
283  && launcher.progress == 0
284  && !notifications.useModal
285 
286  onInteractiveChanged: { if (interactive) { focus = true; } }
287 
288  Binding {
289  target: applicationsDisplayLoader.item
290  property: "focus"
291  value: true
292  }
293  Binding {
294  target: applicationsDisplayLoader.item
295  property: "objectName"
296  value: "stage"
297  }
298  Binding {
299  target: applicationsDisplayLoader.item
300  property: "dragAreaWidth"
301  value: shell.edgeSize
302  }
303  Binding {
304  target: applicationsDisplayLoader.item
305  property: "maximizedAppTopMargin"
306  // Not just using panel.panelHeight as that changes depending on the focused app.
307  value: panel.indicators.minimizedPanelHeight
308  }
309  Binding {
310  target: applicationsDisplayLoader.item
311  property: "interactive"
312  value: applicationsDisplayLoader.interactive
313  }
314  Binding {
315  target: applicationsDisplayLoader.item
316  property: "spreadEnabled"
317  value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
318  }
319  Binding {
320  target: applicationsDisplayLoader.item
321  property: "inverseProgress"
322  value: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
323  }
324  Binding {
325  target: applicationsDisplayLoader.item
326  property: "shellOrientationAngle"
327  value: shell.orientationAngle
328  }
329  Binding {
330  target: applicationsDisplayLoader.item
331  property: "shellOrientation"
332  value: shell.orientation
333  }
334  Binding {
335  target: applicationsDisplayLoader.item
336  property: "orientations"
337  value: shell.orientations
338  }
339  Binding {
340  target: applicationsDisplayLoader.item
341  property: "background"
342  value: wallpaperResolver.background
343  }
344  Binding {
345  target: applicationsDisplayLoader.item
346  property: "nativeWidth"
347  value: shell.nativeWidth
348  }
349  Binding {
350  target: applicationsDisplayLoader.item
351  property: "nativeHeight"
352  value: shell.nativeHeight
353  }
354  Binding {
355  target: applicationsDisplayLoader.item
356  property: "beingResized"
357  value: shell.beingResized
358  }
359  Binding {
360  target: applicationsDisplayLoader.item
361  property: "keepDashRunning"
362  value: launcher.shown || launcher.dashSwipe
363  }
364  Binding {
365  target: applicationsDisplayLoader.item
366  property: "suspended"
367  value: greeter.shown
368  }
369  Binding {
370  target: applicationsDisplayLoader.item
371  property: "altTabPressed"
372  value: physicalKeysMapper.altTabPressed
373  }
374  Binding {
375  target: applicationsDisplayLoader.item
376  property: "leftMargin"
377  value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
378  }
379  Binding {
380  target: applicationsDisplayLoader.item
381  property: "applicationManager"
382  value: ApplicationManager
383  }
384  Binding {
385  target: applicationsDisplayLoader.item
386  property: "topLevelSurfaceList"
387  value: topLevelSurfaceList
388  }
389  Binding {
390  target: applicationsDisplayLoader.item
391  property: "oskEnabled"
392  value: shell.oskEnabled
393  }
394  }
395  }
396 
397  InputMethod {
398  id: inputMethod
399  objectName: "inputMethod"
400  anchors {
401  fill: parent
402  topMargin: panel.panelHeight
403  leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
404  }
405  z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
406  }
407 
408  Loader {
409  id: greeterLoader
410  anchors.fill: parent
411  anchors.topMargin: panel.panelHeight
412  sourceComponent: shell.mode != "shell" ? integratedGreeter :
413  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
414  onLoaded: {
415  item.objectName = "greeter"
416  }
417  }
418 
419  Component {
420  id: integratedGreeter
421  Greeter {
422 
423  hides: [launcher, panel.indicators]
424  tabletMode: shell.usageScenario != "phone"
425  launcherOffset: launcher.progress
426  forcedUnlock: wizard.active || shell.mode === "full-shell"
427  background: wallpaperResolver.background
428  allowFingerprint: !dialogs.hasActiveDialog &&
429  !notifications.topmostIsFullscreen &&
430  !panel.indicators.shown
431 
432  // avoid overlapping with Launcher's edge drag area
433  // FIXME: Fix TouchRegistry & friends and remove this workaround
434  // Issue involves launcher's DDA getting disabled on a long
435  // left-edge drag
436  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
437 
438  onSessionStarted: {
439  launcher.hide();
440  }
441 
442  onTease: {
443  if (!tutorial.running) {
444  launcher.tease();
445  }
446  }
447 
448  onEmergencyCall: startLockedApp("dialer-app")
449  }
450  }
451 
452  Timer {
453  // See powerConnection for why this is useful
454  id: showGreeterDelayed
455  interval: 1
456  onTriggered: {
457  greeter.forceShow();
458  }
459  }
460 
461  Connections {
462  id: callConnection
463  target: callManager
464 
465  onHasCallsChanged: {
466  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
467  // We just received an incoming call while locked. The
468  // indicator will have already launched dialer-app for us, but
469  // there is a race between "hasCalls" changing and the dialer
470  // starting up. So in case we lose that race, we'll start/
471  // focus the dialer ourselves here too. Even if the indicator
472  // didn't launch the dialer for some reason (or maybe a call
473  // started via some other means), if an active call is
474  // happening, we want to be in the dialer.
475  startLockedApp("dialer-app")
476  }
477  }
478  }
479 
480  Connections {
481  id: powerConnection
482  target: Powerd
483 
484  onStatusChanged: {
485  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
486  !callManager.hasCalls && !wizard.active) {
487  // We don't want to simply call greeter.showNow() here, because
488  // that will take too long. Qt will delay button event
489  // handling until the greeter is done loading and may think the
490  // user held down the power button the whole time, leading to a
491  // power dialog being shown. Instead, delay showing the
492  // greeter until we've finished handling the event. We could
493  // make the greeter load asynchronously instead, but that
494  // introduces a whole host of timing issues, especially with
495  // its animations. So this is simpler.
496  showGreeterDelayed.start();
497  }
498  }
499  }
500 
501  function showHome() {
502  greeter.notifyUserRequestedApp("unity8-dash");
503 
504  var animate = !LightDMService.greeter.active && !stages.shown
505  dash.setCurrentScope(0, animate, false)
506  ApplicationManager.requestFocusApplication("unity8-dash")
507  }
508 
509  function showDash() {
510  if (greeter.notifyShowingDashFromDrag()) {
511  launcher.fadeOut();
512  }
513 
514  if (!greeter.locked && tutorial.launcherLongSwipeEnabled
515  && ApplicationManager.focusedApplicationId != "unity8-dash") {
516  ApplicationManager.requestFocusApplication("unity8-dash")
517  launcher.fadeOut();
518  }
519  }
520 
521  Item {
522  id: overlay
523  z: 10
524 
525  anchors.fill: parent
526 
527  Panel {
528  id: panel
529  objectName: "panel"
530  anchors.fill: parent //because this draws indicator menus
531  indicators {
532  hides: [launcher]
533  available: tutorial.panelEnabled
534  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
535  && (!greeter || !greeter.hasLockedApp)
536  && !shell.waitingOnGreeter
537  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
538 
539  minimizedPanelHeight: units.gu(3)
540  expandedPanelHeight: units.gu(7)
541 
542  indicatorsModel: Indicators.IndicatorsModel {
543  // tablet and phone both use the same profile
544  // FIXME: use just "phone" for greeter too, but first fix
545  // greeter app launching to either load the app inside the
546  // greeter or tell the session to load the app. This will
547  // involve taking the url-dispatcher dbus name and using
548  // SessionBroadcast to tell the session.
549  profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
550  Component.onCompleted: load();
551  }
552  }
553 
554  callHint {
555  greeterShown: greeter.shown
556  }
557 
558  readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
559  ? MirFocusController.focusedSurface.state === Mir.FullscreenState
560  : false
561  fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
562  || greeter.hasLockedApp
563  locked: greeter && greeter.active
564  }
565 
566  Launcher {
567  id: launcher
568  objectName: "launcher"
569 
570  /*
571  * Since the Dash doesn't have the same controll over surfaces that the
572  * Shell does, it can't slowly move the scope out of the way, as the shell
573  * does with apps, and the dash is show instantly. This allows for some
574  * leeway and prevents accidental home swipes.
575  */
576  readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
577  readonly property bool dashSwipe: progress > offset
578 
579  anchors.top: parent.top
580  anchors.topMargin: inverted ? 0 : panel.panelHeight
581  anchors.bottom: parent.bottom
582  width: parent.width
583  dragAreaWidth: shell.edgeSize
584  available: tutorial.launcherEnabled
585  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
586  && !greeter.hasLockedApp
587  && !shell.waitingOnGreeter
588  inverted: shell.usageScenario !== "desktop"
589  superPressed: physicalKeysMapper.superPressed
590  superTabPressed: physicalKeysMapper.superTabPressed
591  panelWidth: units.gu(settings.launcherWidth)
592  lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
593 
594  onShowDashHome: showHome()
595  onDash: showDash()
596  onDashSwipeChanged: {
597  if (dashSwipe) {
598  dash.setCurrentScope(0, false, true)
599  }
600  }
601  onLauncherApplicationSelected: {
602  greeter.notifyUserRequestedApp(appId);
603  shell.activateApplication(appId);
604  }
605  onShownChanged: {
606  if (shown) {
607  panel.indicators.hide()
608  }
609  }
610  onFocusChanged: {
611  if (!focus) {
612  applicationsDisplayLoader.focus = true;
613  }
614  }
615 
616  GlobalShortcut {
617  shortcut: Qt.AltModifier | Qt.Key_F1
618  onTriggered: {
619  launcher.openForKeyboardNavigation();
620  }
621  }
622  GlobalShortcut {
623  shortcut: Qt.MetaModifier | Qt.Key_0
624  onTriggered: {
625  if (LauncherModel.get(9)) {
626  activateApplication(LauncherModel.get(9).appId);
627  }
628  }
629  }
630  Repeater {
631  model: 9
632  GlobalShortcut {
633  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
634  onTriggered: {
635  if (LauncherModel.get(index)) {
636  activateApplication(LauncherModel.get(index).appId);
637  }
638  }
639  }
640  }
641  }
642 
643  KeyboardShortcutsOverlay {
644  objectName: "shortcutsOverlay"
645  enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
646  && height < parent.height - padding - panel.panelHeight
647  anchors.centerIn: parent
648  anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
649  anchors.verticalCenterOffset: panel.panelHeight/2
650  visible: opacity > 0
651  opacity: enabled ? 0.95 : 0
652 
653  Behavior on opacity {
654  UbuntuNumberAnimation {}
655  }
656  }
657 
658  Tutorial {
659  id: tutorial
660  objectName: "tutorial"
661  anchors.fill: parent
662 
663  paused: callManager.hasCalls || !greeter || greeter.shown ||
664  wizard.active
665  delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
666  inputMethod.state === "shown" ||
667  (launcher.shown && !launcher.lockedVisible) ||
668  panel.indicators.shown || stage.dragProgress > 0
669  usageScenario: shell.usageScenario
670  lastInputTimestamp: inputFilter.lastInputTimestamp
671  launcher: launcher
672  panel: panel
673  stage: applicationsDisplayLoader.item
674  }
675 
676  Wizard {
677  id: wizard
678  objectName: "wizard"
679  anchors.fill: parent
680  deferred: shell.mode === "greeter"
681 
682  function unlockWhenDoneWithWizard() {
683  if (!active) {
684  Connectivity.unlockAllModems();
685  }
686  }
687 
688  Component.onCompleted: unlockWhenDoneWithWizard()
689  onActiveChanged: unlockWhenDoneWithWizard()
690  }
691 
692  MouseArea { // modal notifications prevent interacting with other contents
693  anchors.fill: parent
694  visible: notifications.useModal
695  enabled: visible
696  }
697 
698  Notifications {
699  id: notifications
700 
701  model: NotificationBackend.Model
702  margin: units.gu(1)
703  hasMouse: shell.hasMouse
704  background: wallpaperResolver.background
705 
706  y: topmostIsFullscreen ? 0 : panel.panelHeight
707  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
708 
709  states: [
710  State {
711  name: "narrow"
712  when: overlay.width <= units.gu(60)
713  AnchorChanges {
714  target: notifications
715  anchors.left: parent.left
716  anchors.right: parent.right
717  }
718  },
719  State {
720  name: "wide"
721  when: overlay.width > units.gu(60)
722  AnchorChanges {
723  target: notifications
724  anchors.left: undefined
725  anchors.right: parent.right
726  }
727  PropertyChanges { target: notifications; width: units.gu(38) }
728  }
729  ]
730  }
731  }
732 
733  Dialogs {
734  id: dialogs
735  objectName: "dialogs"
736  anchors.fill: parent
737  z: overlay.z + 10
738  usageScenario: shell.usageScenario
739  onPowerOffClicked: {
740  shutdownFadeOutRectangle.enabled = true;
741  shutdownFadeOutRectangle.visible = true;
742  shutdownFadeOut.start();
743  }
744  }
745 
746  Connections {
747  target: SessionBroadcast
748  onShowHome: showHome()
749  }
750 
751  ItemGrabber {
752  id: itemGrabber
753  anchors.fill: parent
754  z: dialogs.z + 10
755  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
756  Connections {
757  target: applicationsDisplayLoader.item
758  ignoreUnknownSignals: true
759  onItemSnapshotRequested: itemGrabber.capture(item)
760  }
761  }
762 
763  Cursor {
764  id: cursor
765  visible: shell.hasMouse
766  z: itemGrabber.z + 1
767 
768  property bool mouseNeverMoved: true
769  Binding {
770  target: cursor; property: "x"; value: shell.width / 2
771  when: cursor.mouseNeverMoved && cursor.visible
772  }
773  Binding {
774  target: cursor; property: "y"; value: shell.height / 2
775  when: cursor.mouseNeverMoved && cursor.visible
776  }
777 
778  onPushedLeftBoundary: {
779  if (buttons === Qt.NoButton) {
780  launcher.pushEdge(amount);
781  }
782  }
783 
784  onPushedRightBoundary: {
785  if (buttons === Qt.NoButton && applicationsDisplayLoader.item
786  && applicationsDisplayLoader.item.pushRightEdge) {
787  applicationsDisplayLoader.item.pushRightEdge(amount);
788  }
789  }
790 
791  onMouseMoved: {
792  mouseNeverMoved = false;
793  cursor.opacity = 1;
794  }
795  }
796 
797  // non-visual object
798  KeymapSwitcher {}
799 
800  Rectangle {
801  id: shutdownFadeOutRectangle
802  z: cursor.z + 1
803  enabled: false
804  visible: false
805  color: "black"
806  anchors.fill: parent
807  opacity: 0.0
808  NumberAnimation on opacity {
809  id: shutdownFadeOut
810  from: 0.0
811  to: 1.0
812  onStopped: {
813  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
814  DBusUnitySessionService.shutdown();
815  }
816  }
817  }
818  }
819 }