Unity 8
Shell.qml
1 /*
2  * Copyright (C) 2013-2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import QtQuick.Window 2.2
19 import AccountsService 0.1
20 import Unity.Application 0.1
21 import Ubuntu.Components 1.3
22 import Ubuntu.Components.Popups 1.3
23 import Ubuntu.Gestures 0.1
24 import Ubuntu.Telephony 0.1 as Telephony
25 import Unity.Connectivity 0.1
26 import Unity.Launcher 0.1
27 import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28 import GSettings 1.0
29 import Utils 0.1
30 import Powerd 0.1
31 import SessionBroadcast 0.1
32 import "Greeter"
33 import "Launcher"
34 import "Panel"
35 import "Components"
36 import "Notifications"
37 import "Stages"
38 import "Tutorial"
39 import "Wizard"
40 import Unity.Notifications 1.0 as NotificationBackend
41 import Unity.Session 0.1
42 import Unity.DashCommunicator 0.1
43 import Unity.Indicators 0.1 as Indicators
44 import Cursor 1.1
45 import WindowManager 0.1
46 
47 
48 StyledItem {
49  id: shell
50 
51  theme.name: "Ubuntu.Components.Themes.SuruDark"
52 
53  // to be set from outside
54  property int orientationAngle: 0
55  property int orientation
56  property Orientations orientations
57  property real nativeWidth
58  property real nativeHeight
59  property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
60  property bool beingResized
61  property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
62  property string mode: "full-greeter"
63  property alias oskEnabled: inputMethod.enabled
64  function updateFocusedAppOrientation() {
65  applicationsDisplayLoader.item.updateFocusedAppOrientation();
66  }
67  function updateFocusedAppOrientationAnimated() {
68  applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
69  }
70  property bool hasMouse: false
71 
72  // to be read from outside
73  readonly property int mainAppWindowOrientationAngle:
74  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
75 
76  readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
77  && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
78  && (!greeter || !greeter.animating)
79 
80  readonly property bool showingGreeter: greeter && greeter.shown
81 
82  property bool startingUp: true
83  Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
84 
85  property int supportedOrientations: {
86  if (startingUp) {
87  // Ensure we don't rotate during start up
88  return Qt.PrimaryOrientation;
89  } else if (greeter && greeter.shown) {
90  return Qt.PrimaryOrientation;
91  } else if (applicationsDisplayLoader.item) {
92  return shell.orientations.map(applicationsDisplayLoader.item.supportedOrientations);
93  } else {
94  // we just don't care
95  return Qt.PortraitOrientation
96  | Qt.LandscapeOrientation
97  | Qt.InvertedPortraitOrientation
98  | Qt.InvertedLandscapeOrientation;
99  }
100  }
101 
102  readonly property var mainApp:
103  applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
104  onMainAppChanged: {
105  if (mainApp) {
106  _onMainAppChanged(mainApp.appId);
107  }
108  }
109  Connections {
110  target: ApplicationManager
111  onFocusRequested: {
112  if (shell.mainApp && shell.mainApp.appId === appId) {
113  _onMainAppChanged(appId);
114  }
115  }
116  }
117  function _onMainAppChanged(appId) {
118  if (wizard.active && appId != "" && appId != "unity8-dash") {
119  // If this happens on first boot, we may be in edge
120  // tutorial or wizard while receiving a call. But a call
121  // is more important than wizard so just bail out of those.
122  tutorial.finish();
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  // Disable everything while greeter is waiting, so that the user can't swipe
142  // the greeter or launcher until we know whether the session is locked.
143  enabled: greeter && !greeter.waiting
144 
145  property real edgeSize: units.gu(settings.edgeDragWidth)
146 
147  WallpaperResolver {
148  id: wallpaperResolver
149  width: shell.width
150  }
151 
152  readonly property alias greeter: greeterLoader.item
153 
154  function activateApplication(appId) {
155  if (ApplicationManager.findApplication(appId)) {
156  ApplicationManager.requestFocusApplication(appId);
157  } else {
158  ApplicationManager.startApplication(appId);
159  }
160  }
161 
162  function startLockedApp(app) {
163  if (greeter.locked) {
164  greeter.lockedApp = app;
165  }
166  shell.activateApplication(app);
167  }
168 
169  Binding {
170  target: LauncherModel
171  property: "applicationManager"
172  value: ApplicationManager
173  }
174 
175  Component.onCompleted: {
176  finishStartUpTimer.start();
177  }
178 
179  LightDM{id: lightDM} // Provide backend access
180  VolumeControl {
181  id: volumeControl
182  indicators: panel.indicators
183  }
184 
185  DashCommunicator {
186  id: dash
187  objectName: "dashCommunicator"
188  }
189 
190  PhysicalKeysMapper {
191  id: physicalKeysMapper
192  objectName: "physicalKeysMapper"
193 
194  onPowerKeyLongPressed: dialogs.showPowerDialog();
195  onVolumeDownTriggered: volumeControl.volumeDown();
196  onVolumeUpTriggered: volumeControl.volumeUp();
197  onScreenshotTriggered: itemGrabber.capture(shell);
198  }
199 
200  GlobalShortcut {
201  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
202  }
203 
204  WindowInputFilter {
205  id: inputFilter
206  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
207  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
208  }
209 
210  WindowInputMonitor {
211  objectName: "windowInputMonitor"
212  onHomeKeyActivated: {
213  // Ignore when greeter is active, to avoid pocket presses
214  if (!greeter.active) {
215  launcher.fadeOut();
216  shell.showHome();
217  }
218  }
219  onTouchBegun: { cursor.opacity = 0; }
220  onTouchEnded: {
221  // move the (hidden) cursor to the last known touch position
222  var mappedCoords = mapFromItem(null, pos.x, pos.y);
223  cursor.x = mappedCoords.x;
224  cursor.y = mappedCoords.y;
225  }
226  }
227 
228  GSettings {
229  id: settings
230  schema.id: "com.canonical.Unity8"
231  }
232 
233  Item {
234  id: stages
235  objectName: "stages"
236  width: parent.width
237  height: parent.height
238 
239  TopLevelSurfaceList {
240  id: topLevelSurfaceList
241  objectName: "topLevelSurfaceList"
242  applicationsModel: ApplicationManager
243  }
244 
245  Loader {
246  id: applicationsDisplayLoader
247  objectName: "applicationsDisplayLoader"
248  anchors.fill: parent
249 
250  // When we have a locked app, we only want to show that one app.
251  // FIXME: do this in a less traumatic way. We currently only allow
252  // locked apps in phone mode (see FIXME in Lockscreen component in
253  // this same file). When that changes, we need to do something
254  // nicer here. But this code is currently just to prevent a
255  // theoretical attack where user enters lockedApp mode, then makes
256  // the screen larger (maybe connects to monitor) and tries to enter
257  // tablet mode.
258 
259  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
260  ? "phone"
261  : shell.usageScenario
262  readonly property string qmlComponent: {
263  if(shell.mode === "greeter") {
264  return "Stages/ShimStage.qml"
265  } else if (applicationsDisplayLoader.usageScenario === "phone") {
266  return "Stages/PhoneStage.qml";
267  } else if (applicationsDisplayLoader.usageScenario === "tablet") {
268  return "Stages/TabletStage.qml";
269  } else {
270  return "Stages/DesktopStage.qml";
271  }
272  }
273  // TODO: Ensure the current stage is destroyed before the new one gets loaded.
274  // Currently the new one will get loaded while the old is still hanging
275  // around for a bit, which might lead to conflicts where both stages
276  // change the model simultaneously.
277  onQmlComponentChanged: {
278  if (item) item.stageAboutToBeUnloaded();
279  source = qmlComponent;
280  }
281 
282  property bool interactive: (!greeter || !greeter.shown)
283  && panel.indicators.fullyClosed
284  && launcher.progress == 0
285  && !notifications.useModal
286 
287  onInteractiveChanged: { if (interactive) { focus = true; } }
288 
289  Binding {
290  target: applicationsDisplayLoader.item
291  property: "focus"
292  value: true
293  }
294  Binding {
295  target: applicationsDisplayLoader.item
296  property: "objectName"
297  value: "stage"
298  }
299  Binding {
300  target: applicationsDisplayLoader.item
301  property: "dragAreaWidth"
302  value: shell.edgeSize
303  }
304  Binding {
305  target: applicationsDisplayLoader.item
306  property: "maximizedAppTopMargin"
307  // Not just using panel.panelHeight as that changes depending on the focused app.
308  value: panel.indicators.minimizedPanelHeight
309  }
310  Binding {
311  target: applicationsDisplayLoader.item
312  property: "interactive"
313  value: applicationsDisplayLoader.interactive
314  }
315  Binding {
316  target: applicationsDisplayLoader.item
317  property: "spreadEnabled"
318  value: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
319  }
320  Binding {
321  target: applicationsDisplayLoader.item
322  property: "inverseProgress"
323  value: greeter && greeter.locked ? 0 : launcher.progress
324  }
325  Binding {
326  target: applicationsDisplayLoader.item
327  property: "shellOrientationAngle"
328  value: shell.orientationAngle
329  }
330  Binding {
331  target: applicationsDisplayLoader.item
332  property: "shellOrientation"
333  value: shell.orientation
334  }
335  Binding {
336  target: applicationsDisplayLoader.item
337  property: "orientations"
338  value: shell.orientations
339  }
340  Binding {
341  target: applicationsDisplayLoader.item
342  property: "background"
343  value: wallpaperResolver.background
344  }
345  Binding {
346  target: applicationsDisplayLoader.item
347  property: "nativeWidth"
348  value: shell.nativeWidth
349  }
350  Binding {
351  target: applicationsDisplayLoader.item
352  property: "nativeHeight"
353  value: shell.nativeHeight
354  }
355  Binding {
356  target: applicationsDisplayLoader.item
357  property: "beingResized"
358  value: shell.beingResized
359  }
360  Binding {
361  target: applicationsDisplayLoader.item
362  property: "keepDashRunning"
363  value: launcher.shown || launcher.dashSwipe
364  }
365  Binding {
366  target: applicationsDisplayLoader.item
367  property: "suspended"
368  value: greeter.shown
369  }
370  Binding {
371  target: applicationsDisplayLoader.item
372  property: "altTabPressed"
373  value: physicalKeysMapper.altTabPressed
374  }
375  Binding {
376  target: applicationsDisplayLoader.item
377  property: "leftMargin"
378  value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
379  }
380  Binding {
381  target: applicationsDisplayLoader.item
382  property: "applicationManager"
383  value: ApplicationManager
384  }
385  Binding {
386  target: applicationsDisplayLoader.item
387  property: "topLevelSurfaceList"
388  value: topLevelSurfaceList
389  }
390  }
391  }
392 
393  InputMethod {
394  id: inputMethod
395  objectName: "inputMethod"
396  anchors {
397  fill: parent
398  topMargin: panel.panelHeight
399  leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
400  }
401  z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
402  }
403 
404  Loader {
405  id: greeterLoader
406  anchors.fill: parent
407  anchors.topMargin: panel.panelHeight
408  sourceComponent: shell.mode != "shell" ? integratedGreeter :
409  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
410  onLoaded: {
411  item.objectName = "greeter"
412  }
413  }
414 
415  Component {
416  id: integratedGreeter
417  Greeter {
418 
419  hides: [launcher, panel.indicators]
420  tabletMode: shell.usageScenario != "phone"
421  launcherOffset: launcher.progress
422  forcedUnlock: wizard.active
423  background: wallpaperResolver.background
424 
425  // avoid overlapping with Launcher's edge drag area
426  // FIXME: Fix TouchRegistry & friends and remove this workaround
427  // Issue involves launcher's DDA getting disabled on a long
428  // left-edge drag
429  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
430 
431  onSessionStarted: {
432  launcher.hide();
433  }
434 
435  onTease: {
436  if (!tutorial.running) {
437  launcher.tease();
438  }
439  }
440 
441  onEmergencyCall: startLockedApp("dialer-app")
442  }
443  }
444 
445  Timer {
446  // See powerConnection for why this is useful
447  id: showGreeterDelayed
448  interval: 1
449  onTriggered: {
450  greeter.forceShow();
451  }
452  }
453 
454  Connections {
455  id: callConnection
456  target: callManager
457 
458  onHasCallsChanged: {
459  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
460  // We just received an incoming call while locked. The
461  // indicator will have already launched dialer-app for us, but
462  // there is a race between "hasCalls" changing and the dialer
463  // starting up. So in case we lose that race, we'll start/
464  // focus the dialer ourselves here too. Even if the indicator
465  // didn't launch the dialer for some reason (or maybe a call
466  // started via some other means), if an active call is
467  // happening, we want to be in the dialer.
468  startLockedApp("dialer-app")
469  }
470  }
471  }
472 
473  Connections {
474  id: powerConnection
475  target: Powerd
476 
477  onStatusChanged: {
478  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
479  !callManager.hasCalls && !wizard.active) {
480  // We don't want to simply call greeter.showNow() here, because
481  // that will take too long. Qt will delay button event
482  // handling until the greeter is done loading and may think the
483  // user held down the power button the whole time, leading to a
484  // power dialog being shown. Instead, delay showing the
485  // greeter until we've finished handling the event. We could
486  // make the greeter load asynchronously instead, but that
487  // introduces a whole host of timing issues, especially with
488  // its animations. So this is simpler.
489  showGreeterDelayed.start();
490  }
491  }
492  }
493 
494  function showHome() {
495  greeter.notifyUserRequestedApp("unity8-dash");
496 
497  var animate = !lightDM.greeter.active && !stages.shown
498  dash.setCurrentScope(0, animate, false)
499  ApplicationManager.requestFocusApplication("unity8-dash")
500  }
501 
502  function showDash() {
503  if (greeter.notifyShowingDashFromDrag()) {
504  launcher.fadeOut();
505  }
506 
507  if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
508  ApplicationManager.requestFocusApplication("unity8-dash")
509  launcher.fadeOut();
510  }
511  }
512 
513  Item {
514  id: overlay
515  z: 10
516 
517  anchors.fill: parent
518 
519  Panel {
520  id: panel
521  objectName: "panel"
522  anchors.fill: parent //because this draws indicator menus
523  indicators {
524  hides: [launcher]
525  available: tutorial.panelEnabled
526  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
527  && (!greeter || !greeter.hasLockedApp)
528  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
529 
530  minimizedPanelHeight: units.gu(3)
531  expandedPanelHeight: units.gu(7)
532 
533  indicatorsModel: Indicators.IndicatorsModel {
534  // tablet and phone both use the same profile
535  profile: "phone"
536  Component.onCompleted: load();
537  }
538  }
539 
540  callHint {
541  greeterShown: greeter.shown
542  }
543 
544  readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
545  ? MirFocusController.focusedSurface.state === Mir.FullscreenState
546  : false
547  fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
548  || greeter.hasLockedApp
549  locked: greeter && greeter.active
550  }
551 
552  Launcher {
553  id: launcher
554  objectName: "launcher"
555 
556  readonly property bool dashSwipe: progress > 0
557 
558  anchors.top: parent.top
559  anchors.topMargin: inverted ? 0 : panel.panelHeight
560  anchors.bottom: parent.bottom
561  width: parent.width
562  dragAreaWidth: shell.edgeSize
563  available: tutorial.launcherEnabled
564  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
565  && !greeter.hasLockedApp
566  inverted: shell.usageScenario !== "desktop"
567  superPressed: physicalKeysMapper.superPressed
568  superTabPressed: physicalKeysMapper.superTabPressed
569  panelWidth: units.gu(settings.launcherWidth)
570  lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
571 
572  onShowDashHome: showHome()
573  onDash: showDash()
574  onDashSwipeChanged: {
575  if (dashSwipe) {
576  dash.setCurrentScope(0, false, true)
577  }
578  }
579  onLauncherApplicationSelected: {
580  greeter.notifyUserRequestedApp(appId);
581  shell.activateApplication(appId);
582  }
583  onShownChanged: {
584  if (shown) {
585  panel.indicators.hide()
586  }
587  }
588  onFocusChanged: {
589  if (!focus) {
590  applicationsDisplayLoader.focus = true;
591  }
592  }
593 
594  GlobalShortcut {
595  shortcut: Qt.AltModifier | Qt.Key_F1
596  onTriggered: {
597  launcher.openForKeyboardNavigation();
598  }
599  }
600  GlobalShortcut {
601  shortcut: Qt.MetaModifier | Qt.Key_0
602  onTriggered: {
603  if (LauncherModel.get(9)) {
604  activateApplication(LauncherModel.get(9).appId);
605  }
606  }
607  }
608  Repeater {
609  model: 9
610  GlobalShortcut {
611  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
612  onTriggered: {
613  if (LauncherModel.get(index)) {
614  activateApplication(LauncherModel.get(index).appId);
615  }
616  }
617  }
618  }
619  }
620 
621  Tutorial {
622  id: tutorial
623  objectName: "tutorial"
624  anchors.fill: parent
625 
626  paused: callManager.hasCalls || greeter.shown
627  keyboardVisible: inputMethod.state === "shown"
628  usageScenario: shell.usageScenario
629  lastInputTimestamp: inputFilter.lastInputTimestamp
630  launcher: launcher
631  panel: panel
632  stage: applicationsDisplayLoader.item
633  }
634 
635  Wizard {
636  id: wizard
637  objectName: "wizard"
638  anchors.fill: parent
639 
640  function unlockWhenDoneWithWizard() {
641  if (!active) {
642  Connectivity.unlockAllModems();
643  }
644  }
645 
646  Component.onCompleted: unlockWhenDoneWithWizard()
647  onActiveChanged: unlockWhenDoneWithWizard()
648  }
649 
650  MouseArea { // modal notifications prevent interacting with other contents
651  anchors.fill: parent
652  visible: notifications.useModal
653  enabled: visible
654  }
655 
656  Notifications {
657  id: notifications
658 
659  model: NotificationBackend.Model
660  margin: units.gu(1)
661  hasMouse: shell.hasMouse
662  background: wallpaperResolver.background
663 
664  y: topmostIsFullscreen ? 0 : panel.panelHeight
665  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
666 
667  states: [
668  State {
669  name: "narrow"
670  when: overlay.width <= units.gu(60)
671  AnchorChanges {
672  target: notifications
673  anchors.left: parent.left
674  anchors.right: parent.right
675  }
676  },
677  State {
678  name: "wide"
679  when: overlay.width > units.gu(60)
680  AnchorChanges {
681  target: notifications
682  anchors.left: undefined
683  anchors.right: parent.right
684  }
685  PropertyChanges { target: notifications; width: units.gu(38) }
686  }
687  ]
688  }
689  }
690 
691  Dialogs {
692  id: dialogs
693  objectName: "dialogs"
694  anchors.fill: parent
695  z: overlay.z + 10
696  usageScenario: shell.usageScenario
697  onPowerOffClicked: {
698  shutdownFadeOutRectangle.enabled = true;
699  shutdownFadeOutRectangle.visible = true;
700  shutdownFadeOut.start();
701  }
702  }
703 
704  Connections {
705  target: SessionBroadcast
706  onShowHome: showHome()
707  }
708 
709  ItemGrabber {
710  id: itemGrabber
711  anchors.fill: parent
712  z: dialogs.z + 10
713  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
714  Connections {
715  target: applicationsDisplayLoader.item
716  ignoreUnknownSignals: true
717  onItemSnapshotRequested: itemGrabber.capture(item)
718  }
719  }
720 
721  Cursor {
722  id: cursor
723  visible: shell.hasMouse
724  z: itemGrabber.z + 1
725 
726  onPushedLeftBoundary: {
727  if (buttons === Qt.NoButton) {
728  launcher.pushEdge(amount);
729  }
730  }
731 
732  onPushedRightBoundary: {
733  if (buttons === Qt.NoButton && applicationsDisplayLoader.item
734  && applicationsDisplayLoader.item.pushRightEdge) {
735  applicationsDisplayLoader.item.pushRightEdge(amount);
736  }
737  }
738 
739  onMouseMoved: { cursor.opacity = 1; }
740  }
741 
742  // non-visual object
743  KeymapSwitcher {}
744 
745  Rectangle {
746  id: shutdownFadeOutRectangle
747  z: cursor.z + 1
748  enabled: false
749  visible: false
750  color: "black"
751  anchors.fill: parent
752  opacity: 0.0
753  NumberAnimation on opacity {
754  id: shutdownFadeOut
755  from: 0.0
756  to: 1.0
757  onStopped: {
758  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
759  DBusUnitySessionService.shutdown();
760  }
761  }
762  }
763  }
764 }