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