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  // Disable everything while greeter is waiting, so that the user can't swipe
141  // the greeter or launcher until we know whether the session is locked.
142  enabled: 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  LightDM{id: lightDM} // Provide backend access
179  VolumeControl {
180  id: volumeControl
181  indicators: panel.indicators
182  }
183 
184  DashCommunicator {
185  id: dash
186  objectName: "dashCommunicator"
187  }
188 
189  PhysicalKeysMapper {
190  id: physicalKeysMapper
191  objectName: "physicalKeysMapper"
192 
193  onPowerKeyLongPressed: dialogs.showPowerDialog();
194  onVolumeDownTriggered: volumeControl.volumeDown();
195  onVolumeUpTriggered: volumeControl.volumeUp();
196  onScreenshotTriggered: itemGrabber.capture(shell);
197  }
198 
199  GlobalShortcut {
200  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
201  }
202 
203  WindowInputFilter {
204  id: inputFilter
205  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
206  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
207  }
208 
209  WindowInputMonitor {
210  objectName: "windowInputMonitor"
211  onHomeKeyActivated: {
212  // Ignore when greeter is active, to avoid pocket presses
213  if (!greeter.active) {
214  launcher.fadeOut();
215  shell.showHome();
216  }
217  }
218  onTouchBegun: { cursor.opacity = 0; }
219  onTouchEnded: {
220  // move the (hidden) cursor to the last known touch position
221  var mappedCoords = mapFromItem(null, pos.x, pos.y);
222  cursor.x = mappedCoords.x;
223  cursor.y = mappedCoords.y;
224  cursor.mouseNeverMoved = false;
225  }
226  }
227 
228  GSettings {
229  id: settings
230  schema.id: "com.canonical.Unity8"
231  }
232 
233  Item {
234  id: stages
235  objectName: "stages"
236  width: parent.width
237  height: parent.height
238 
239  TopLevelSurfaceList {
240  id: topLevelSurfaceList
241  objectName: "topLevelSurfaceList"
242  applicationsModel: ApplicationManager
243  }
244 
245  Loader {
246  id: applicationsDisplayLoader
247  objectName: "applicationsDisplayLoader"
248  anchors.fill: parent
249 
250  // When we have a locked app, we only want to show that one app.
251  // FIXME: do this in a less traumatic way. We currently only allow
252  // locked apps in phone mode (see FIXME in Lockscreen component in
253  // this same file). When that changes, we need to do something
254  // nicer here. But this code is currently just to prevent a
255  // theoretical attack where user enters lockedApp mode, then makes
256  // the screen larger (maybe connects to monitor) and tries to enter
257  // tablet mode.
258 
259  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
260  ? "phone"
261  : shell.usageScenario
262  readonly property string qmlComponent: {
263  if(shell.mode === "greeter") {
264  return "Stages/ShimStage.qml"
265  } else if (applicationsDisplayLoader.usageScenario === "phone") {
266  return "Stages/PhoneStage.qml";
267  } else if (applicationsDisplayLoader.usageScenario === "tablet") {
268  return "Stages/TabletStage.qml";
269  } else {
270  return "Stages/DesktopStage.qml";
271  }
272  }
273  // TODO: Ensure the current stage is destroyed before the new one gets loaded.
274  // Currently the new one will get loaded while the old is still hanging
275  // around for a bit, which might lead to conflicts where both stages
276  // change the model simultaneously.
277  onQmlComponentChanged: {
278  if (item) item.stageAboutToBeUnloaded();
279  source = qmlComponent;
280  }
281 
282  property bool interactive: (!greeter || !greeter.shown)
283  && panel.indicators.fullyClosed
284  && launcher.progress == 0
285  && !notifications.useModal
286 
287  onInteractiveChanged: { if (interactive) { focus = true; } }
288 
289  Binding {
290  target: applicationsDisplayLoader.item
291  property: "focus"
292  value: true
293  }
294  Binding {
295  target: applicationsDisplayLoader.item
296  property: "objectName"
297  value: "stage"
298  }
299  Binding {
300  target: applicationsDisplayLoader.item
301  property: "dragAreaWidth"
302  value: shell.edgeSize
303  }
304  Binding {
305  target: applicationsDisplayLoader.item
306  property: "maximizedAppTopMargin"
307  // Not just using panel.panelHeight as that changes depending on the focused app.
308  value: panel.indicators.minimizedPanelHeight
309  }
310  Binding {
311  target: applicationsDisplayLoader.item
312  property: "interactive"
313  value: applicationsDisplayLoader.interactive
314  }
315  Binding {
316  target: applicationsDisplayLoader.item
317  property: "spreadEnabled"
318  value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
319  }
320  Binding {
321  target: applicationsDisplayLoader.item
322  property: "inverseProgress"
323  value: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
324  }
325  Binding {
326  target: applicationsDisplayLoader.item
327  property: "shellOrientationAngle"
328  value: shell.orientationAngle
329  }
330  Binding {
331  target: applicationsDisplayLoader.item
332  property: "shellOrientation"
333  value: shell.orientation
334  }
335  Binding {
336  target: applicationsDisplayLoader.item
337  property: "orientations"
338  value: shell.orientations
339  }
340  Binding {
341  target: applicationsDisplayLoader.item
342  property: "background"
343  value: wallpaperResolver.background
344  }
345  Binding {
346  target: applicationsDisplayLoader.item
347  property: "nativeWidth"
348  value: shell.nativeWidth
349  }
350  Binding {
351  target: applicationsDisplayLoader.item
352  property: "nativeHeight"
353  value: shell.nativeHeight
354  }
355  Binding {
356  target: applicationsDisplayLoader.item
357  property: "beingResized"
358  value: shell.beingResized
359  }
360  Binding {
361  target: applicationsDisplayLoader.item
362  property: "keepDashRunning"
363  value: launcher.shown || launcher.dashSwipe
364  }
365  Binding {
366  target: applicationsDisplayLoader.item
367  property: "suspended"
368  value: greeter.shown
369  }
370  Binding {
371  target: applicationsDisplayLoader.item
372  property: "altTabPressed"
373  value: physicalKeysMapper.altTabPressed
374  }
375  Binding {
376  target: applicationsDisplayLoader.item
377  property: "leftMargin"
378  value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
379  }
380  Binding {
381  target: applicationsDisplayLoader.item
382  property: "applicationManager"
383  value: ApplicationManager
384  }
385  Binding {
386  target: applicationsDisplayLoader.item
387  property: "topLevelSurfaceList"
388  value: topLevelSurfaceList
389  }
390  }
391  }
392 
393  InputMethod {
394  id: inputMethod
395  objectName: "inputMethod"
396  anchors {
397  fill: parent
398  topMargin: panel.panelHeight
399  leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
400  }
401  z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
402  }
403 
404  Loader {
405  id: greeterLoader
406  anchors.fill: parent
407  anchors.topMargin: panel.panelHeight
408  sourceComponent: shell.mode != "shell" ? integratedGreeter :
409  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
410  onLoaded: {
411  item.objectName = "greeter"
412  }
413  }
414 
415  Component {
416  id: integratedGreeter
417  Greeter {
418 
419  hides: [launcher, panel.indicators]
420  tabletMode: shell.usageScenario != "phone"
421  launcherOffset: launcher.progress
422  forcedUnlock: wizard.active || shell.mode === "full-shell"
423  background: wallpaperResolver.background
424 
425  // avoid overlapping with Launcher's edge drag area
426  // FIXME: Fix TouchRegistry & friends and remove this workaround
427  // Issue involves launcher's DDA getting disabled on a long
428  // left-edge drag
429  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
430 
431  onSessionStarted: {
432  launcher.hide();
433  }
434 
435  onTease: {
436  if (!tutorial.running) {
437  launcher.tease();
438  }
439  }
440 
441  onEmergencyCall: startLockedApp("dialer-app")
442  }
443  }
444 
445  Timer {
446  // See powerConnection for why this is useful
447  id: showGreeterDelayed
448  interval: 1
449  onTriggered: {
450  greeter.forceShow();
451  }
452  }
453 
454  Connections {
455  id: callConnection
456  target: callManager
457 
458  onHasCallsChanged: {
459  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
460  // We just received an incoming call while locked. The
461  // indicator will have already launched dialer-app for us, but
462  // there is a race between "hasCalls" changing and the dialer
463  // starting up. So in case we lose that race, we'll start/
464  // focus the dialer ourselves here too. Even if the indicator
465  // didn't launch the dialer for some reason (or maybe a call
466  // started via some other means), if an active call is
467  // happening, we want to be in the dialer.
468  startLockedApp("dialer-app")
469  }
470  }
471  }
472 
473  Connections {
474  id: powerConnection
475  target: Powerd
476 
477  onStatusChanged: {
478  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
479  !callManager.hasCalls && !wizard.active) {
480  // We don't want to simply call greeter.showNow() here, because
481  // that will take too long. Qt will delay button event
482  // handling until the greeter is done loading and may think the
483  // user held down the power button the whole time, leading to a
484  // power dialog being shown. Instead, delay showing the
485  // greeter until we've finished handling the event. We could
486  // make the greeter load asynchronously instead, but that
487  // introduces a whole host of timing issues, especially with
488  // its animations. So this is simpler.
489  showGreeterDelayed.start();
490  }
491  }
492  }
493 
494  function showHome() {
495  greeter.notifyUserRequestedApp("unity8-dash");
496 
497  var animate = !lightDM.greeter.active && !stages.shown
498  dash.setCurrentScope(0, animate, false)
499  ApplicationManager.requestFocusApplication("unity8-dash")
500  }
501 
502  function showDash() {
503  if (greeter.notifyShowingDashFromDrag()) {
504  launcher.fadeOut();
505  }
506 
507  if (!greeter.locked && tutorial.launcherLongSwipeEnabled
508  && ApplicationManager.focusedApplicationId != "unity8-dash") {
509  ApplicationManager.requestFocusApplication("unity8-dash")
510  launcher.fadeOut();
511  }
512  }
513 
514  Item {
515  id: overlay
516  z: 10
517 
518  anchors.fill: parent
519 
520  Panel {
521  id: panel
522  objectName: "panel"
523  anchors.fill: parent //because this draws indicator menus
524  indicators {
525  hides: [launcher]
526  available: tutorial.panelEnabled
527  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
528  && (!greeter || !greeter.hasLockedApp)
529  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
530 
531  minimizedPanelHeight: units.gu(3)
532  expandedPanelHeight: units.gu(7)
533 
534  indicatorsModel: Indicators.IndicatorsModel {
535  // tablet and phone both use the same profile
536  profile: "phone"
537  Component.onCompleted: load();
538  }
539  }
540 
541  callHint {
542  greeterShown: greeter.shown
543  }
544 
545  readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
546  ? MirFocusController.focusedSurface.state === Mir.FullscreenState
547  : false
548  fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
549  || greeter.hasLockedApp
550  locked: greeter && greeter.active
551  }
552 
553  Launcher {
554  id: launcher
555  objectName: "launcher"
556 
557  /*
558  * Since the Dash doesn't have the same controll over surfaces that the
559  * Shell does, it can't slowly move the scope out of the way, as the shell
560  * does with apps, and the dash is show instantly. This allows for some
561  * leeway and prevents accidental home swipes.
562  */
563  readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
564  readonly property bool dashSwipe: progress > offset
565 
566  anchors.top: parent.top
567  anchors.topMargin: inverted ? 0 : panel.panelHeight
568  anchors.bottom: parent.bottom
569  width: parent.width
570  dragAreaWidth: shell.edgeSize
571  available: tutorial.launcherEnabled
572  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
573  && !greeter.hasLockedApp
574  inverted: shell.usageScenario !== "desktop"
575  superPressed: physicalKeysMapper.superPressed
576  superTabPressed: physicalKeysMapper.superTabPressed
577  panelWidth: units.gu(settings.launcherWidth)
578  lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
579 
580  onShowDashHome: showHome()
581  onDash: showDash()
582  onDashSwipeChanged: {
583  if (dashSwipe) {
584  dash.setCurrentScope(0, false, true)
585  }
586  }
587  onLauncherApplicationSelected: {
588  greeter.notifyUserRequestedApp(appId);
589  shell.activateApplication(appId);
590  }
591  onShownChanged: {
592  if (shown) {
593  panel.indicators.hide()
594  }
595  }
596  onFocusChanged: {
597  if (!focus) {
598  applicationsDisplayLoader.focus = true;
599  }
600  }
601 
602  GlobalShortcut {
603  shortcut: Qt.AltModifier | Qt.Key_F1
604  onTriggered: {
605  launcher.openForKeyboardNavigation();
606  }
607  }
608  GlobalShortcut {
609  shortcut: Qt.MetaModifier | Qt.Key_0
610  onTriggered: {
611  if (LauncherModel.get(9)) {
612  activateApplication(LauncherModel.get(9).appId);
613  }
614  }
615  }
616  Repeater {
617  model: 9
618  GlobalShortcut {
619  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
620  onTriggered: {
621  if (LauncherModel.get(index)) {
622  activateApplication(LauncherModel.get(index).appId);
623  }
624  }
625  }
626  }
627  }
628 
629  KeyboardShortcutsOverlay {
630  objectName: "shortcutsOverlay"
631  enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
632  && height < parent.height - padding - panel.panelHeight
633  anchors.centerIn: parent
634  anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
635  anchors.verticalCenterOffset: panel.panelHeight/2
636  visible: opacity > 0
637  opacity: enabled ? 0.95 : 0
638 
639  Behavior on opacity {
640  UbuntuNumberAnimation {}
641  }
642  }
643 
644  Tutorial {
645  id: tutorial
646  objectName: "tutorial"
647  anchors.fill: parent
648 
649  paused: callManager.hasCalls || !greeter || greeter.shown ||
650  wizard.active
651  delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
652  inputMethod.state === "shown" ||
653  (launcher.shown && !launcher.lockedVisible) ||
654  panel.indicators.shown || stage.dragProgress > 0
655  usageScenario: shell.usageScenario
656  lastInputTimestamp: inputFilter.lastInputTimestamp
657  launcher: launcher
658  panel: panel
659  stage: applicationsDisplayLoader.item
660  }
661 
662  Wizard {
663  id: wizard
664  objectName: "wizard"
665  anchors.fill: parent
666 
667  function unlockWhenDoneWithWizard() {
668  if (!active) {
669  Connectivity.unlockAllModems();
670  }
671  }
672 
673  Component.onCompleted: unlockWhenDoneWithWizard()
674  onActiveChanged: unlockWhenDoneWithWizard()
675  }
676 
677  MouseArea { // modal notifications prevent interacting with other contents
678  anchors.fill: parent
679  visible: notifications.useModal
680  enabled: visible
681  }
682 
683  Notifications {
684  id: notifications
685 
686  model: NotificationBackend.Model
687  margin: units.gu(1)
688  hasMouse: shell.hasMouse
689  background: wallpaperResolver.background
690 
691  y: topmostIsFullscreen ? 0 : panel.panelHeight
692  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
693 
694  states: [
695  State {
696  name: "narrow"
697  when: overlay.width <= units.gu(60)
698  AnchorChanges {
699  target: notifications
700  anchors.left: parent.left
701  anchors.right: parent.right
702  }
703  },
704  State {
705  name: "wide"
706  when: overlay.width > units.gu(60)
707  AnchorChanges {
708  target: notifications
709  anchors.left: undefined
710  anchors.right: parent.right
711  }
712  PropertyChanges { target: notifications; width: units.gu(38) }
713  }
714  ]
715  }
716  }
717 
718  Dialogs {
719  id: dialogs
720  objectName: "dialogs"
721  anchors.fill: parent
722  z: overlay.z + 10
723  usageScenario: shell.usageScenario
724  onPowerOffClicked: {
725  shutdownFadeOutRectangle.enabled = true;
726  shutdownFadeOutRectangle.visible = true;
727  shutdownFadeOut.start();
728  }
729  }
730 
731  Connections {
732  target: SessionBroadcast
733  onShowHome: showHome()
734  }
735 
736  ItemGrabber {
737  id: itemGrabber
738  anchors.fill: parent
739  z: dialogs.z + 10
740  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
741  Connections {
742  target: applicationsDisplayLoader.item
743  ignoreUnknownSignals: true
744  onItemSnapshotRequested: itemGrabber.capture(item)
745  }
746  }
747 
748  Cursor {
749  id: cursor
750  visible: shell.hasMouse
751  z: itemGrabber.z + 1
752 
753  property bool mouseNeverMoved: true
754  Binding {
755  target: cursor; property: "x"; value: shell.width / 2
756  when: cursor.mouseNeverMoved && cursor.visible
757  }
758  Binding {
759  target: cursor; property: "y"; value: shell.height / 2
760  when: cursor.mouseNeverMoved && cursor.visible
761  }
762 
763  onPushedLeftBoundary: {
764  if (buttons === Qt.NoButton) {
765  launcher.pushEdge(amount);
766  }
767  }
768 
769  onPushedRightBoundary: {
770  if (buttons === Qt.NoButton && applicationsDisplayLoader.item
771  && applicationsDisplayLoader.item.pushRightEdge) {
772  applicationsDisplayLoader.item.pushRightEdge(amount);
773  }
774  }
775 
776  onMouseMoved: {
777  mouseNeverMoved = false;
778  cursor.opacity = 1;
779  }
780  }
781 
782  // non-visual object
783  KeymapSwitcher {}
784 
785  Rectangle {
786  id: shutdownFadeOutRectangle
787  z: cursor.z + 1
788  enabled: false
789  visible: false
790  color: "black"
791  anchors.fill: parent
792  opacity: 0.0
793  NumberAnimation on opacity {
794  id: shutdownFadeOut
795  from: 0.0
796  to: 1.0
797  onStopped: {
798  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
799  DBusUnitySessionService.shutdown();
800  }
801  }
802  }
803  }
804 }