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