Unity 8
Shell.qml
1 /*
2  * Copyright (C) 2013-2016 Canonical, Ltd.
3  * Copyright (C) 2019-2021 UBports Foundation
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 import QtQuick 2.12
19 import QtQuick.Window 2.2
20 import AccountsService 0.1
21 import Unity.Application 0.1
22 import Ubuntu.Components 1.3
23 import Ubuntu.Components.Popups 1.3
24 import Ubuntu.Gestures 0.1
25 import Ubuntu.Telephony 0.1 as Telephony
26 import Unity.Connectivity 0.1
27 import Unity.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 Unity.Notifications 1.0 as NotificationBackend
42 import Unity.Session 0.1
43 import Unity.Indicators 0.1 as Indicators
44 import Cursor 1.1
45 import WindowManager 1.0
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 panelAreaShowProgress: panel.panelAreaShowProgress
60  property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
61  property string mode: "full-greeter"
62  property bool interactiveBlur: false
63  property alias oskEnabled: inputMethod.enabled
64  function updateFocusedAppOrientation() {
65  stage.updateFocusedAppOrientation();
66  }
67  function updateFocusedAppOrientationAnimated() {
68  stage.updateFocusedAppOrientationAnimated();
69  }
70  property bool hasMouse: false
71  property bool hasKeyboard: false
72  property bool hasTouchscreen: false
73  property bool supportsMultiColorLed: true
74 
75  // The largest dimension, in pixels, of all of the screens this Shell is
76  // operating on.
77  // If a script sets the shell to 240x320 when it was 320x240, we could
78  // end up in a situation where our dimensions are 240x240 for a short time.
79  // Notifying the Wallpaper of both events would make it reload the image
80  // twice. So, we use a Binding { delayed: true }.
81  property real largestScreenDimension
82  Binding {
83  target: shell
84  delayed: true
85  property: "largestScreenDimension"
86  value: Math.max(nativeWidth, nativeHeight)
87  }
88 
89  // Used by tests
90  property alias lightIndicators: indicatorsModel.light
91 
92  // to be read from outside
93  readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
94 
95  readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
96  && stage.orientationChangesEnabled
97  && (!greeter.animating)
98 
99  readonly property bool showingGreeter: greeter && greeter.shown
100 
101  property bool startingUp: true
102  Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
103 
104  property int supportedOrientations: {
105  if (startingUp) {
106  // Ensure we don't rotate during start up
107  return Qt.PrimaryOrientation;
108  } else if (notifications.topmostIsFullscreen) {
109  return Qt.PrimaryOrientation;
110  } else {
111  return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
112  }
113  }
114 
115  readonly property var mainApp: stage.mainApp
116 
117  onMainAppChanged: {
118  _onMainAppChanged((mainApp ? mainApp.appId : ""));
119  }
120  Connections {
121  target: ApplicationManager
122  onFocusRequested: {
123  if (shell.mainApp && shell.mainApp.appId === appId) {
124  _onMainAppChanged(appId);
125  }
126  }
127  }
128 
129  // Calls attention back to the most important thing that's been focused
130  // (ex: phone calls go over Wizard, app focuses go over indicators, greeter
131  // goes over everything if it is locked)
132  // Must be called whenever app focus changes occur, even if the focus change
133  // is "nothing is focused". In that case, call with appId = ""
134  function _onMainAppChanged(appId) {
135 
136  if (appId !== "") {
137  if (wizard.active) {
138  // If this happens on first boot, we may be in the
139  // wizard while receiving a call. A call is more
140  // important than the wizard so just bail out of it.
141  wizard.hide();
142  }
143 
144  if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
145  // If we are in the middle of a call, make dialer lockedApp. The
146  // Greeter will show it when it's notified of the focus.
147  // This can happen if user backs out of dialer back to greeter, then
148  // launches dialer again.
149  greeter.lockedApp = appId;
150  }
151 
152  panel.indicators.hide();
153  launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
154  }
155 
156  // *Always* make sure the greeter knows that the focused app changed
157  if (greeter) greeter.notifyAppFocusRequested(appId);
158  }
159 
160  // For autopilot consumption
161  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
162 
163  // Note when greeter is waiting on PAM, so that we can disable edges until
164  // we know which user data to show and whether the session is locked.
165  readonly property bool waitingOnGreeter: greeter && greeter.waiting
166 
167  // True when the user is logged in with no apps running
168  readonly property bool atDesktop: topLevelSurfaceList && greeter && topLevelSurfaceList.count === 0 && !greeter.active
169 
170  onAtDesktopChanged: {
171  if (atDesktop && stage) {
172  stage.closeSpread();
173  }
174  }
175 
176  property real edgeSize: units.gu(settings.edgeDragWidth)
177 
178  WallpaperResolver {
179  id: wallpaperResolver
180  objectName: "wallpaperResolver"
181 
182  readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
183  readonly property bool hasCustomBackground: background != defaultBackground
184 
185  GSettings {
186  id: backgroundSettings
187  schema.id: "org.gnome.desktop.background"
188  }
189 
190  candidates: [
191  AccountsService.backgroundFile,
192  backgroundSettings.pictureUri,
193  defaultBackground
194  ]
195  }
196 
197  readonly property alias greeter: greeterLoader.item
198 
199  function activateApplication(appId) {
200  topLevelSurfaceList.pendingActivation();
201 
202  // Either open the app in our own session, or -- if we're acting as a
203  // greeter -- ask the user's session to open it for us.
204  if (shell.mode === "greeter") {
205  activateURL("application:///" + appId + ".desktop");
206  } else {
207  startApp(appId);
208  }
209  stage.focus = true;
210  }
211 
212  function activateURL(url) {
213  SessionBroadcast.requestUrlStart(AccountsService.user, url);
214  greeter.notifyUserRequestedApp();
215  panel.indicators.hide();
216  }
217 
218  function startApp(appId) {
219  if (ApplicationManager.findApplication(appId)) {
220  ApplicationManager.requestFocusApplication(appId);
221  } else {
222  ApplicationManager.startApplication(appId);
223  }
224  }
225 
226  function startLockedApp(app) {
227  topLevelSurfaceList.pendingActivation();
228 
229  if (greeter.locked) {
230  greeter.lockedApp = app;
231  }
232  startApp(app); // locked apps are always in our same session
233  }
234 
235  Binding {
236  target: LauncherModel
237  property: "applicationManager"
238  value: ApplicationManager
239  }
240 
241  Component.onCompleted: {
242  finishStartUpTimer.start();
243  }
244 
245  VolumeControl {
246  id: volumeControl
247  }
248 
249  PhysicalKeysMapper {
250  id: physicalKeysMapper
251  objectName: "physicalKeysMapper"
252 
253  onPowerKeyLongPressed: dialogs.showPowerDialog();
254  onVolumeDownTriggered: volumeControl.volumeDown();
255  onVolumeUpTriggered: volumeControl.volumeUp();
256  onScreenshotTriggered: itemGrabber.capture(shell);
257  }
258 
259  GlobalShortcut {
260  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
261  }
262 
263  WindowInputFilter {
264  id: inputFilter
265  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
266  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
267  }
268 
269  WindowInputMonitor {
270  objectName: "windowInputMonitor"
271  onHomeKeyActivated: {
272  // Ignore when greeter is active, to avoid pocket presses
273  if (!greeter.active) {
274  launcher.toggleDrawer(/* focusInputField */ false,
275  /* onlyOpen */ false,
276  /* alsoToggleLauncher */ true);
277  }
278  }
279  onTouchBegun: { cursor.opacity = 0; }
280  onTouchEnded: {
281  // move the (hidden) cursor to the last known touch position
282  var mappedCoords = mapFromItem(null, pos.x, pos.y);
283  cursor.x = mappedCoords.x;
284  cursor.y = mappedCoords.y;
285  cursor.mouseNeverMoved = false;
286  }
287  }
288 
289  AvailableDesktopArea {
290  id: availableDesktopAreaItem
291  anchors.fill: parent
292  anchors.topMargin: panel.fullscreenMode ? 0 : panel.minimizedPanelHeight
293  anchors.leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
294  }
295 
296  GSettings {
297  id: settings
298  schema.id: "com.canonical.Unity8"
299  }
300 
301  Item {
302  id: stages
303  objectName: "stages"
304  width: parent.width
305  height: parent.height
306 
307  SurfaceManager {
308  id: surfaceMan
309  objectName: "surfaceManager"
310  }
311  TopLevelWindowModel {
312  id: topLevelSurfaceList
313  objectName: "topLevelSurfaceList"
314  applicationManager: ApplicationManager // it's a singleton
315  surfaceManager: surfaceMan
316  }
317 
318  Stage {
319  id: stage
320  objectName: "stage"
321  anchors.fill: parent
322  focus: true
323 
324  dragAreaWidth: shell.edgeSize
325  background: wallpaperResolver.background
326  backgroundSourceSize: shell.largestScreenDimension
327 
328  applicationManager: ApplicationManager
329  topLevelSurfaceList: topLevelSurfaceList
330  inputMethodRect: inputMethod.visibleRect
331  rightEdgePushProgress: rightEdgeBarrier.progress
332  availableDesktopArea: availableDesktopAreaItem
333  launcherLeftMargin: launcher.visibleWidth
334 
335  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
336  ? "phone"
337  : shell.usageScenario
338 
339  mode: usageScenario == "phone" ? "staged"
340  : usageScenario == "tablet" ? "stagedWithSideStage"
341  : "windowed"
342 
343  shellOrientation: shell.orientation
344  shellOrientationAngle: shell.orientationAngle
345  orientations: shell.orientations
346  nativeWidth: shell.nativeWidth
347  nativeHeight: shell.nativeHeight
348 
349  allowInteractivity: (!greeter || !greeter.shown)
350  && panel.indicators.fullyClosed
351  && !notifications.useModal
352  && !launcher.takesFocus
353 
354  suspended: greeter.shown
355  altTabPressed: physicalKeysMapper.altTabPressed
356  oskEnabled: shell.oskEnabled
357  spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
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: shell.mode != "shell" ? integratedGreeter :
411  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
412  onLoaded: {
413  item.objectName = "greeter"
414  }
415  property bool toggleDrawerAfterUnlock: false
416  Connections {
417  target: greeter
418  onActiveChanged: {
419  if (greeter.active)
420  return
421 
422  // Show drawer in case showHome() requests it
423  if (greeterLoader.toggleDrawerAfterUnlock) {
424  launcher.toggleDrawer(false);
425  greeterLoader.toggleDrawerAfterUnlock = false;
426  } else {
427  launcher.hide();
428  }
429  }
430  }
431  }
432 
433  Component {
434  id: integratedGreeter
435  Greeter {
436 
437  enabled: panel.indicators.fullyClosed // hides OSK when panel is open
438  hides: [launcher, panel.indicators, panel.applicationMenus]
439  tabletMode: shell.usageScenario != "phone"
440  usageMode: shell.usageScenario
441  orientation: shell.orientation
442  forcedUnlock: wizard.active || shell.mode === "full-shell"
443  background: wallpaperResolver.background
444  backgroundSourceSize: shell.largestScreenDimension
445  hasCustomBackground: wallpaperResolver.hasCustomBackground
446  inputMethodRect: inputMethod.visibleRect
447  hasKeyboard: shell.hasKeyboard
448  allowFingerprint: !dialogs.hasActiveDialog &&
449  !notifications.topmostIsFullscreen &&
450  !panel.indicators.shown
451  panelHeight: panel.panelHeight
452 
453  // avoid overlapping with Launcher's edge drag area
454  // FIXME: Fix TouchRegistry & friends and remove this workaround
455  // Issue involves launcher's DDA getting disabled on a long
456  // left-edge drag
457  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
458 
459  onTease: {
460  if (!tutorial.running) {
461  launcher.tease();
462  }
463  }
464 
465  onEmergencyCall: startLockedApp("dialer-app")
466  }
467  }
468 
469  Timer {
470  // See powerConnection for why this is useful
471  id: showGreeterDelayed
472  interval: 1
473  onTriggered: {
474  // Go through the dbus service, because it has checks for whether
475  // we are even allowed to lock or not.
476  DBusUnitySessionService.PromptLock();
477  }
478  }
479 
480  Connections {
481  id: callConnection
482  target: callManager
483 
484  onHasCallsChanged: {
485  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
486  // We just received an incoming call while locked. The
487  // indicator will have already launched dialer-app for us, but
488  // there is a race between "hasCalls" changing and the dialer
489  // starting up. So in case we lose that race, we'll start/
490  // focus the dialer ourselves here too. Even if the indicator
491  // didn't launch the dialer for some reason (or maybe a call
492  // started via some other means), if an active call is
493  // happening, we want to be in the dialer.
494  startLockedApp("dialer-app")
495  }
496  }
497  }
498 
499  Connections {
500  id: powerConnection
501  target: Powerd
502 
503  onStatusChanged: {
504  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
505  !callManager.hasCalls && !wizard.active) {
506  // We don't want to simply call greeter.showNow() here, because
507  // that will take too long. Qt will delay button event
508  // handling until the greeter is done loading and may think the
509  // user held down the power button the whole time, leading to a
510  // power dialog being shown. Instead, delay showing the
511  // greeter until we've finished handling the event. We could
512  // make the greeter load asynchronously instead, but that
513  // introduces a whole host of timing issues, especially with
514  // its animations. So this is simpler.
515  showGreeterDelayed.start();
516  }
517  }
518  }
519 
520  function showHome() {
521  greeter.notifyUserRequestedApp();
522 
523  if (shell.mode === "greeter") {
524  SessionBroadcast.requestHomeShown(AccountsService.user);
525  } else {
526  if (!greeter.active) {
527  launcher.toggleDrawer(false);
528  } else {
529  greeterLoader.toggleDrawerAfterUnlock = true;
530  }
531  }
532  }
533 
534  Item {
535  id: overlay
536  z: 10
537 
538  anchors.fill: parent
539 
540  Panel {
541  id: panel
542  objectName: "panel"
543  anchors.fill: parent //because this draws indicator menus
544 
545  mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
546  minimizedPanelHeight: units.gu(3)
547  expandedPanelHeight: units.gu(7)
548  applicationMenuContentX: launcher.lockedVisible ? launcher.panelWidth : 0
549 
550  indicators {
551  hides: [launcher]
552  available: tutorial.panelEnabled
553  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
554  && (!greeter || !greeter.hasLockedApp)
555  && !shell.waitingOnGreeter
556  && settings.enableIndicatorMenu
557 
558  model: Indicators.IndicatorsModel {
559  id: indicatorsModel
560  // tablet and phone both use the same profile
561  // FIXME: use just "phone" for greeter too, but first fix
562  // greeter app launching to either load the app inside the
563  // greeter or tell the session to load the app. This will
564  // involve taking the url-dispatcher dbus name and using
565  // SessionBroadcast to tell the session.
566  profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
567  Component.onCompleted: {
568  load();
569  }
570  }
571  }
572 
573  applicationMenus {
574  hides: [launcher]
575  available: (!greeter || !greeter.shown)
576  && !shell.waitingOnGreeter
577  && !stage.spreadShown
578  }
579 
580  readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
581  ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
582  : false
583  fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
584  || greeter.hasLockedApp
585  greeterShown: greeter && greeter.shown
586  hasKeyboard: shell.hasKeyboard
587  supportsMultiColorLed: shell.supportsMultiColorLed
588  }
589 
590  Launcher {
591  id: launcher
592  objectName: "launcher"
593 
594  anchors.top: parent.top
595  anchors.topMargin: inverted ? 0 : panel.panelHeight
596  anchors.bottom: parent.bottom
597  width: parent.width
598  dragAreaWidth: shell.edgeSize
599  available: tutorial.launcherEnabled
600  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
601  && !greeter.hasLockedApp
602  && !shell.waitingOnGreeter
603  inverted: shell.usageScenario !== "desktop"
604  superPressed: physicalKeysMapper.superPressed
605  superTabPressed: physicalKeysMapper.superTabPressed
606  panelWidth: units.gu(settings.launcherWidth)
607  lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
608  blurSource: greeter.shown ? greeter : stages
609  interactiveBlur: shell.interactiveBlur
610  topPanelHeight: panel.panelHeight
611  drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
612  privateMode: greeter.active
613  background: wallpaperResolver.background
614  backgroundSourceSize: shell.largestScreenDimension
615 
616  // It can be assumed that the Launcher and Panel would overlap if
617  // the Panel is open and taking up the full width of the shell
618  readonly property bool collidingWithPanel: panel && (!panel.fullyClosed && !panel.partialWidth)
619 
620  // The "autohideLauncher" setting is only valid in desktop mode
621  readonly property bool lockedByUser: (shell.usageScenario == "desktop" && !settings.autohideLauncher)
622 
623  // The Launcher should absolutely not be locked visible under some
624  // conditions
625  readonly property bool lockAllowed: !collidingWithPanel && !panel.fullscreenMode && !wizard.active && !tutorial.demonstrateLauncher
626 
627  onShowDashHome: showHome()
628  onLauncherApplicationSelected: {
629  greeter.notifyUserRequestedApp();
630  shell.activateApplication(appId);
631  }
632  onShownChanged: {
633  if (shown) {
634  panel.indicators.hide();
635  panel.applicationMenus.hide();
636  }
637  }
638  onDrawerShownChanged: {
639  if (drawerShown) {
640  panel.indicators.hide();
641  panel.applicationMenus.hide();
642  }
643  }
644  onFocusChanged: {
645  if (!focus) {
646  stage.focus = true;
647  }
648  }
649 
650  GlobalShortcut {
651  shortcut: Qt.MetaModifier | Qt.Key_A
652  onTriggered: {
653  launcher.toggleDrawer(true);
654  }
655  }
656  GlobalShortcut {
657  shortcut: Qt.AltModifier | Qt.Key_F1
658  onTriggered: {
659  launcher.openForKeyboardNavigation();
660  }
661  }
662  GlobalShortcut {
663  shortcut: Qt.MetaModifier | Qt.Key_0
664  onTriggered: {
665  if (LauncherModel.get(9)) {
666  activateApplication(LauncherModel.get(9).appId);
667  }
668  }
669  }
670  Repeater {
671  model: 9
672  GlobalShortcut {
673  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
674  onTriggered: {
675  if (LauncherModel.get(index)) {
676  activateApplication(LauncherModel.get(index).appId);
677  }
678  }
679  }
680  }
681  }
682 
683  KeyboardShortcutsOverlay {
684  objectName: "shortcutsOverlay"
685  enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
686  && height < parent.height - padding - panel.panelHeight
687  anchors.centerIn: parent
688  anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
689  anchors.verticalCenterOffset: panel.panelHeight/2
690  visible: opacity > 0
691  opacity: enabled ? 0.95 : 0
692 
693  Behavior on opacity {
694  UbuntuNumberAnimation {}
695  }
696  }
697 
698  Tutorial {
699  id: tutorial
700  objectName: "tutorial"
701  anchors.fill: parent
702 
703  paused: callManager.hasCalls || !greeter || greeter.active || wizard.active
704  || !hasTouchscreen // TODO #1661557 something better for no touchscreen
705  delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
706  inputMethod.visible ||
707  (launcher.shown && !launcher.lockedVisible) ||
708  panel.indicators.shown || stage.rightEdgeDragProgress > 0
709  usageScenario: shell.usageScenario
710  lastInputTimestamp: inputFilter.lastInputTimestamp
711  launcher: launcher
712  panel: panel
713  stage: stage
714  }
715 
716  Wizard {
717  id: wizard
718  objectName: "wizard"
719  anchors.fill: parent
720  deferred: shell.mode === "greeter"
721 
722  function unlockWhenDoneWithWizard() {
723  if (!active) {
724  Connectivity.unlockAllModems();
725  }
726  }
727 
728  Component.onCompleted: unlockWhenDoneWithWizard()
729  onActiveChanged: unlockWhenDoneWithWizard()
730  }
731 
732  MouseArea { // modal notifications prevent interacting with other contents
733  anchors.fill: parent
734  visible: notifications.useModal
735  enabled: visible
736  }
737 
738  Notifications {
739  id: notifications
740 
741  model: NotificationBackend.Model
742  margin: units.gu(1)
743  hasMouse: shell.hasMouse
744  background: wallpaperResolver.background
745 
746  y: topmostIsFullscreen ? 0 : panel.panelHeight
747  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
748 
749  states: [
750  State {
751  name: "narrow"
752  when: overlay.width <= units.gu(60)
753  AnchorChanges {
754  target: notifications
755  anchors.left: parent.left
756  anchors.right: parent.right
757  }
758  },
759  State {
760  name: "wide"
761  when: overlay.width > units.gu(60)
762  AnchorChanges {
763  target: notifications
764  anchors.left: undefined
765  anchors.right: parent.right
766  }
767  PropertyChanges { target: notifications; width: units.gu(38) }
768  }
769  ]
770  }
771 
772  EdgeBarrier {
773  id: rightEdgeBarrier
774  enabled: !greeter.shown
775 
776  // NB: it does its own positioning according to the specified edge
777  edge: Qt.RightEdge
778 
779  onPassed: {
780  panel.indicators.hide()
781  }
782 
783  material: Component {
784  Item {
785  Rectangle {
786  width: parent.height
787  height: parent.width
788  rotation: 90
789  anchors.centerIn: parent
790  gradient: Gradient {
791  GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
792  GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
793  }
794  }
795  }
796  }
797  }
798  }
799 
800  Dialogs {
801  id: dialogs
802  objectName: "dialogs"
803  anchors.fill: parent
804  visible: hasActiveDialog
805  z: overlay.z + 10
806  usageScenario: shell.usageScenario
807  hasKeyboard: shell.hasKeyboard
808  onPowerOffClicked: {
809  shutdownFadeOutRectangle.enabled = true;
810  shutdownFadeOutRectangle.visible = true;
811  shutdownFadeOut.start();
812  }
813  }
814 
815  Connections {
816  target: SessionBroadcast
817  onShowHome: if (shell.mode !== "greeter") showHome()
818  }
819 
820  URLDispatcher {
821  id: urlDispatcher
822  objectName: "urlDispatcher"
823  active: shell.mode === "greeter"
824  onUrlRequested: shell.activateURL(url)
825  }
826 
827  ItemGrabber {
828  id: itemGrabber
829  anchors.fill: parent
830  z: dialogs.z + 10
831  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
832  Connections {
833  target: stage
834  ignoreUnknownSignals: true
835  onItemSnapshotRequested: itemGrabber.capture(item)
836  }
837  }
838 
839  Timer {
840  id: cursorHidingTimer
841  interval: 3000
842  running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
843  onTriggered: cursor.opacity = 0;
844  }
845 
846  Cursor {
847  id: cursor
848  objectName: "cursor"
849  visible: shell.hasMouse
850  z: itemGrabber.z + 1
851  topBoundaryOffset: panel.panelHeight
852 
853  confiningItem: stage.itemConfiningMouseCursor
854 
855  property bool mouseNeverMoved: true
856  Binding {
857  target: cursor; property: "x"; value: shell.width / 2
858  when: cursor.mouseNeverMoved && cursor.visible
859  }
860  Binding {
861  target: cursor; property: "y"; value: shell.height / 2
862  when: cursor.mouseNeverMoved && cursor.visible
863  }
864 
865  height: units.gu(3)
866 
867  readonly property var previewRectangle: stage.previewRectangle.target &&
868  stage.previewRectangle.target.dragging ?
869  stage.previewRectangle : null
870 
871  onPushedLeftBoundary: {
872  if (buttons === Qt.NoButton) {
873  launcher.pushEdge(amount);
874  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
875  previewRectangle.maximizeLeft(amount);
876  }
877  }
878 
879  onPushedRightBoundary: {
880  if (buttons === Qt.NoButton) {
881  rightEdgeBarrier.push(amount);
882  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
883  previewRectangle.maximizeRight(amount);
884  }
885  }
886 
887  onPushedTopBoundary: {
888  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
889  previewRectangle.maximize(amount);
890  }
891  }
892  onPushedTopLeftCorner: {
893  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
894  previewRectangle.maximizeTopLeft(amount);
895  }
896  }
897  onPushedTopRightCorner: {
898  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
899  previewRectangle.maximizeTopRight(amount);
900  }
901  }
902  onPushedBottomLeftCorner: {
903  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
904  previewRectangle.maximizeBottomLeft(amount);
905  }
906  }
907  onPushedBottomRightCorner: {
908  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
909  previewRectangle.maximizeBottomRight(amount);
910  }
911  }
912  onPushStopped: {
913  if (previewRectangle) {
914  previewRectangle.stop();
915  }
916  }
917 
918  onMouseMoved: {
919  mouseNeverMoved = false;
920  cursor.opacity = 1;
921  }
922 
923  Behavior on opacity { UbuntuNumberAnimation {} }
924  }
925 
926  // non-visual objects
927  KeymapSwitcher {
928  focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
929  }
930  BrightnessControl {}
931 
932  Rectangle {
933  id: shutdownFadeOutRectangle
934  z: cursor.z + 1
935  enabled: false
936  visible: false
937  color: "black"
938  anchors.fill: parent
939  opacity: 0.0
940  NumberAnimation on opacity {
941  id: shutdownFadeOut
942  from: 0.0
943  to: 1.0
944  onStopped: {
945  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
946  DBusUnitySessionService.shutdown();
947  }
948  }
949  }
950  }
951 }