Unity 8
Shell.qml
1 /*
2  * Copyright (C) 2013-2015 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.0
18 import QtQuick.Window 2.0
19 import AccountsService 0.1
20 import GSettings 1.0
21 import Unity.Application 0.1
22 import Ubuntu.Components 0.1
23 import Ubuntu.Components.Popups 1.0
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 WindowKeysFilter
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 
45 
46 Item {
47  id: shell
48 
49  // to be set from outside
50  property int orientationAngle: 0
51  property int orientation
52  property int primaryOrientation
53  property int nativeOrientation
54  property real nativeWidth
55  property real nativeHeight
56  property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
57  property bool beingResized
58  property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59  property string mode: "full-greeter"
60  function updateFocusedAppOrientation() {
61  applicationsDisplayLoader.item.updateFocusedAppOrientation();
62  }
63  function updateFocusedAppOrientationAnimated() {
64  applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
65  }
66 
67  // to be read from outside
68  readonly property int mainAppWindowOrientationAngle:
69  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
70 
71  readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
72  && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
73  && (!greeter || !greeter.animating)
74 
75  readonly property bool showingGreeter: greeter && greeter.shown
76 
77  property bool startingUp: true
78  Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
79 
80  property int supportedOrientations: {
81  if (startingUp) {
82  // Ensure we don't rotate during start up
83  return Qt.PrimaryOrientation;
84  } else if (greeter && greeter.shown) {
85  return Qt.PrimaryOrientation;
86  } else if (mainApp) {
87  return mainApp.supportedOrientations;
88  } else {
89  // we just don't care
90  return Qt.PortraitOrientation
91  | Qt.LandscapeOrientation
92  | Qt.InvertedPortraitOrientation
93  | Qt.InvertedLandscapeOrientation;
94  }
95  }
96 
97  // For autopilot consumption
98  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
99 
100  // internal props from here onwards
101  readonly property var mainApp:
102  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
103 
104  // Disable everything while greeter is waiting, so that the user can't swipe
105  // the greeter or launcher until we know whether the session is locked.
106  enabled: greeter && !greeter.waiting
107 
108  property real edgeSize: units.gu(2)
109  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
110  property url background: asImageTester.status == Image.Ready ? asImageTester.source
111  : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
112 
113  readonly property alias greeter: greeterLoader.item
114 
115  function activateApplication(appId) {
116  if (ApplicationManager.findApplication(appId)) {
117  ApplicationManager.requestFocusApplication(appId);
118  } else {
119  var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
120  : ApplicationManager.NoFlag;
121  ApplicationManager.startApplication(appId, execFlags);
122  }
123  }
124 
125  function startLockedApp(app) {
126  if (greeter.locked) {
127  greeter.lockedApp = app;
128  }
129  shell.activateApplication(app);
130  }
131 
132  // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
133  Image {
134  id: asImageTester
135  source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
136  height: 0
137  width: 0
138  sourceSize.height: 0
139  sourceSize.width: 0
140  }
141 
142  GSettings {
143  id: backgroundSettings
144  schema.id: "org.gnome.desktop.background"
145  }
146 
147  // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
148  Image {
149  id: gsImageTester
150  source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
151  height: 0
152  width: 0
153  sourceSize.height: 0
154  sourceSize.width: 0
155  }
156 
157  Binding {
158  target: LauncherModel
159  property: "applicationManager"
160  value: ApplicationManager
161  }
162 
163  Component.onCompleted: {
164  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
165  if (ApplicationManager.count > 0) {
166  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
167  }
168  finishStartUpTimer.start();
169  }
170 
171  LightDM{id: lightDM} // Provide backend access
172  VolumeControl {
173  id: volumeControl
174  indicators: panel.indicators
175  }
176 
177  DashCommunicator {
178  id: dash
179  objectName: "dashCommunicator"
180  }
181 
182  PhysicalKeysMapper {
183  id: physicalKeysMapper
184  objectName: "physicalKeysMapper"
185 
186  onPowerKeyLongPressed: dialogs.showPowerDialog();
187  onVolumeDownTriggered: volumeControl.volumeDown();
188  onVolumeUpTriggered: volumeControl.volumeUp();
189  onScreenshotTriggered: screenGrabber.capture();
190  }
191 
192  ScreenGrabber {
193  id: screenGrabber
194  z: dialogs.z + 10
195  }
196 
197  GlobalShortcut {
198  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
199  }
200 
201  WindowKeysFilter {
202  Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
203  Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
204  }
205 
206  HomeKeyWatcher {
207  onActivated: { launcher.fadeOut(); shell.showHome(); }
208  }
209 
210  Item {
211  id: stages
212  objectName: "stages"
213  width: parent.width
214  height: parent.height
215 
216  Connections {
217  target: ApplicationManager
218 
219  // This signal is also fired when we try to focus the current app
220  // again. We rely on this!
221  onFocusedApplicationIdChanged: {
222  var appId = ApplicationManager.focusedApplicationId;
223 
224  if (tutorial.running && appId != "" && appId != "unity8-dash") {
225  // If this happens on first boot, we may be in edge
226  // tutorial or wizard while receiving a call. But a call
227  // is more important than wizard so just bail out of those.
228  tutorial.finish();
229  wizard.hide();
230  }
231 
232  if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
233  // If we are in the middle of a call, make dialer lockedApp and show it.
234  // This can happen if user backs out of dialer back to greeter, then
235  // launches dialer again.
236  greeter.lockedApp = appId;
237  }
238  greeter.notifyAppFocused(appId);
239 
240  panel.indicators.hide();
241  }
242 
243  onApplicationAdded: {
244  launcher.hide();
245  }
246  }
247 
248  Loader {
249  id: applicationsDisplayLoader
250  objectName: "applicationsDisplayLoader"
251  anchors.fill: parent
252 
253  // When we have a locked app, we only want to show that one app.
254  // FIXME: do this in a less traumatic way. We currently only allow
255  // locked apps in phone mode (see FIXME in Lockscreen component in
256  // this same file). When that changes, we need to do something
257  // nicer here. But this code is currently just to prevent a
258  // theoretical attack where user enters lockedApp mode, then makes
259  // the screen larger (maybe connects to monitor) and tries to enter
260  // tablet mode.
261 
262  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
263  ? "phone"
264  : shell.usageScenario
265  source: {
266  if(shell.mode === "greeter") {
267  return "Stages/ShimStage.qml"
268  } else if (applicationsDisplayLoader.usageScenario === "phone") {
269  return "Stages/PhoneStage.qml";
270  } else if (applicationsDisplayLoader.usageScenario === "tablet") {
271  return "Stages/TabletStage.qml";
272  } else {
273  return "Stages/DesktopStage.qml";
274  }
275  }
276 
277  property bool interactive: tutorial.spreadEnabled
278  && (!greeter || !greeter.shown)
279  && panel.indicators.fullyClosed
280  && launcher.progress == 0
281  && !notifications.useModal
282 
283  onInteractiveChanged: { if (interactive) { focus = true; } }
284 
285  Binding {
286  target: applicationsDisplayLoader.item
287  property: "objectName"
288  value: "stage"
289  }
290  Binding {
291  target: applicationsDisplayLoader.item
292  property: "dragAreaWidth"
293  value: shell.edgeSize
294  }
295  Binding {
296  target: applicationsDisplayLoader.item
297  property: "maximizedAppTopMargin"
298  // Not just using panel.panelHeight as that changes depending on the focused app.
299  value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
300  }
301  Binding {
302  target: applicationsDisplayLoader.item
303  property: "interactive"
304  value: applicationsDisplayLoader.interactive
305  }
306  Binding {
307  target: applicationsDisplayLoader.item
308  property: "spreadEnabled"
309  value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
310  }
311  Binding {
312  target: applicationsDisplayLoader.item
313  property: "inverseProgress"
314  value: greeter && greeter.locked ? 0 : launcher.progress
315  }
316  Binding {
317  target: applicationsDisplayLoader.item
318  property: "shellOrientationAngle"
319  value: shell.orientationAngle
320  }
321  Binding {
322  target: applicationsDisplayLoader.item
323  property: "shellOrientation"
324  value: shell.orientation
325  }
326  Binding {
327  target: applicationsDisplayLoader.item
328  property: "background"
329  value: shell.background
330  }
331  Binding {
332  target: applicationsDisplayLoader.item
333  property: "shellPrimaryOrientation"
334  value: shell.primaryOrientation
335  }
336  Binding {
337  target: applicationsDisplayLoader.item
338  property: "nativeOrientation"
339  value: shell.nativeOrientation
340  }
341  Binding {
342  target: applicationsDisplayLoader.item
343  property: "nativeWidth"
344  value: shell.nativeWidth
345  }
346  Binding {
347  target: applicationsDisplayLoader.item
348  property: "nativeHeight"
349  value: shell.nativeHeight
350  }
351  Binding {
352  target: applicationsDisplayLoader.item
353  property: "beingResized"
354  value: shell.beingResized
355  }
356  Binding {
357  target: applicationsDisplayLoader.item
358  property: "keepDashRunning"
359  value: launcher.shown || launcher.dashSwipe
360  }
361  Binding {
362  target: applicationsDisplayLoader.item
363  property: "suspended"
364  value: greeter.shown
365  }
366  Binding {
367  target: applicationsDisplayLoader.item
368  property: "altTabPressed"
369  value: physicalKeysMapper.altTabPressed
370  }
371  }
372 
373  Tutorial {
374  id: tutorial
375  objectName: "tutorial"
376  anchors.fill: parent
377 
378  // EdgeDragAreas don't work with mice. So to avoid trapping the user,
379  // we skip the tutorial on the Desktop to avoid using them. The
380  // Desktop doesn't use the same spread design anyway. The tutorial is
381  // all a bit of a placeholder on non-phone form factors right now.
382  // When the design team gives us more guidance, we can do something
383  // more clever here.
384  active: usageScenario != "desktop" && AccountsService.demoEdges
385 
386  paused: LightDM.Greeter.active
387  launcher: launcher
388  panel: panel
389  edgeSize: shell.edgeSize
390 
391  onFinished: {
392  AccountsService.demoEdges = false;
393  active = false; // for immediate response / if AS is having problems
394  }
395  }
396  }
397 
398  InputMethod {
399  id: inputMethod
400  objectName: "inputMethod"
401  anchors { fill: parent; topMargin: panel.panelHeight }
402  z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
403  }
404 
405  Connections {
406  target: SessionManager
407  onSessionStopping: {
408  if (!session.parentSession && !session.application) {
409  // nothing is using it. delete it right away
410  session.release();
411  }
412  }
413  }
414 
415  Loader {
416  id: greeterLoader
417  anchors.fill: parent
418  anchors.topMargin: panel.panelHeight
419  sourceComponent: shell.mode != "shell" ? integratedGreeter :
420  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
421  onLoaded: {
422  item.objectName = "greeter"
423  }
424  }
425 
426  Component {
427  id: integratedGreeter
428  Greeter {
429 
430  hides: [launcher, panel.indicators]
431  tabletMode: shell.usageScenario != "phone"
432  launcherOffset: launcher.progress
433  forcedUnlock: tutorial.running
434  background: shell.background
435 
436  // avoid overlapping with Launcher's edge drag area
437  // FIXME: Fix TouchRegistry & friends and remove this workaround
438  // Issue involves launcher's DDA getting disabled on a long
439  // left-edge drag
440  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
441 
442  onSessionStarted: {
443  launcher.hide();
444  }
445 
446  onTease: {
447  if (!tutorial.running) {
448  launcher.tease();
449  }
450  }
451 
452  onEmergencyCall: startLockedApp("dialer-app")
453  }
454  }
455 
456  Timer {
457  // See powerConnection for why this is useful
458  id: showGreeterDelayed
459  interval: 1
460  onTriggered: {
461  greeter.forceShow();
462  }
463  }
464 
465  Connections {
466  id: callConnection
467  target: callManager
468 
469  onHasCallsChanged: {
470  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
471  // We just received an incoming call while locked. The
472  // indicator will have already launched dialer-app for us, but
473  // there is a race between "hasCalls" changing and the dialer
474  // starting up. So in case we lose that race, we'll start/
475  // focus the dialer ourselves here too. Even if the indicator
476  // didn't launch the dialer for some reason (or maybe a call
477  // started via some other means), if an active call is
478  // happening, we want to be in the dialer.
479  startLockedApp("dialer-app")
480  }
481  }
482  }
483 
484  Connections {
485  id: powerConnection
486  target: Powerd
487 
488  onStatusChanged: {
489  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
490  !callManager.hasCalls && !tutorial.running) {
491  // We don't want to simply call greeter.showNow() here, because
492  // that will take too long. Qt will delay button event
493  // handling until the greeter is done loading and may think the
494  // user held down the power button the whole time, leading to a
495  // power dialog being shown. Instead, delay showing the
496  // greeter until we've finished handling the event. We could
497  // make the greeter load asynchronously instead, but that
498  // introduces a whole host of timing issues, especially with
499  // its animations. So this is simpler.
500  showGreeterDelayed.start();
501  }
502  }
503  }
504 
505  function showHome() {
506  if (tutorial.running) {
507  return
508  }
509 
510  greeter.notifyAboutToFocusApp("unity8-dash");
511 
512  var animate = !lightDM.greeter.active && !stages.shown
513  dash.setCurrentScope(0, animate, false)
514  ApplicationManager.requestFocusApplication("unity8-dash")
515  }
516 
517  function showDash() {
518  if (greeter.notifyShowingDashFromDrag()) {
519  launcher.fadeOut();
520  }
521 
522  if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
523  ApplicationManager.requestFocusApplication("unity8-dash")
524  launcher.fadeOut();
525  }
526  }
527 
528  Item {
529  id: overlay
530  z: 10
531 
532  anchors.fill: parent
533 
534  Panel {
535  id: panel
536  objectName: "panel"
537  anchors.fill: parent //because this draws indicator menus
538  indicators {
539  hides: [launcher]
540  available: tutorial.panelEnabled
541  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
542  && (!greeter || !greeter.hasLockedApp)
543  contentEnabled: tutorial.panelContentEnabled
544  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
545 
546  minimizedPanelHeight: units.gu(3)
547  expandedPanelHeight: units.gu(7)
548 
549  indicatorsModel: Indicators.IndicatorsModel {
550  // tablet and phone both use the same profile
551  profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
552  Component.onCompleted: load();
553  }
554  }
555 
556  callHint {
557  greeterShown: greeter.shown
558  }
559 
560  property bool topmostApplicationIsFullscreen:
561  ApplicationManager.focusedApplicationId &&
562  ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
563 
564  fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
565  || greeter.hasLockedApp
566  }
567 
568  Launcher {
569  id: launcher
570  objectName: "launcher"
571 
572  readonly property bool dashSwipe: progress > 0
573 
574  anchors.top: parent.top
575  anchors.topMargin: inverted ? 0 : panel.panelHeight
576  anchors.bottom: parent.bottom
577  width: parent.width
578  dragAreaWidth: shell.edgeSize
579  available: tutorial.launcherEnabled
580  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
581  && !greeter.hasLockedApp
582  inverted: shell.usageScenario !== "desktop"
583  shadeBackground: !tutorial.running
584 
585  onShowDashHome: showHome()
586  onDash: showDash()
587  onDashSwipeChanged: {
588  if (dashSwipe) {
589  dash.setCurrentScope(0, false, true)
590  }
591  }
592  onLauncherApplicationSelected: {
593  if (!tutorial.running) {
594  greeter.notifyAboutToFocusApp(appId);
595  shell.activateApplication(appId)
596  }
597  }
598  onShownChanged: {
599  if (shown) {
600  panel.indicators.hide()
601  }
602  }
603  }
604 
605  Wizard {
606  id: wizard
607  objectName: "wizard"
608  anchors.fill: parent
609  background: shell.background
610 
611  function unlockWhenDoneWithWizard() {
612  if (!active) {
613  Connectivity.unlockAllModems();
614  }
615  }
616 
617  Component.onCompleted: unlockWhenDoneWithWizard()
618  onActiveChanged: unlockWhenDoneWithWizard()
619  }
620 
621  Rectangle {
622  id: modalNotificationBackground
623 
624  visible: notifications.useModal
625  color: "#000000"
626  anchors.fill: parent
627  opacity: 0.9
628 
629  MouseArea {
630  anchors.fill: parent
631  }
632  }
633 
634  Notifications {
635  id: notifications
636 
637  model: NotificationBackend.Model
638  margin: units.gu(1)
639 
640  y: topmostIsFullscreen ? 0 : panel.panelHeight
641  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
642 
643  states: [
644  State {
645  name: "narrow"
646  when: overlay.width <= units.gu(60)
647  AnchorChanges {
648  target: notifications
649  anchors.left: parent.left
650  anchors.right: parent.right
651  }
652  },
653  State {
654  name: "wide"
655  when: overlay.width > units.gu(60)
656  AnchorChanges {
657  target: notifications
658  anchors.left: undefined
659  anchors.right: parent.right
660  }
661  PropertyChanges { target: notifications; width: units.gu(38) }
662  }
663  ]
664  }
665  }
666 
667  Dialogs {
668  id: dialogs
669  objectName: "dialogs"
670  anchors.fill: parent
671  z: overlay.z + 10
672  usageScenario: shell.usageScenario
673  onPowerOffClicked: {
674  shutdownFadeOutRectangle.enabled = true;
675  shutdownFadeOutRectangle.visible = true;
676  shutdownFadeOut.start();
677  }
678  }
679 
680  Connections {
681  target: SessionBroadcast
682  onShowHome: showHome()
683  }
684 
685  Rectangle {
686  id: shutdownFadeOutRectangle
687  z: screenGrabber.z + 10
688  enabled: false
689  visible: false
690  color: "black"
691  anchors.fill: parent
692  opacity: 0.0
693  NumberAnimation on opacity {
694  id: shutdownFadeOut
695  from: 0.0
696  to: 1.0
697  onStopped: {
698  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
699  DBusUnitySessionService.shutdown();
700  }
701  }
702  }
703  }
704 
705 }