Unity 8
 All Classes Functions
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.SystemImage 0.1
26 import Ubuntu.Telephony 0.1 as Telephony
27 import Unity.Connectivity 0.1
28 import Unity.Launcher 0.1
29 import Utils 0.1
30 import LightDM 0.1 as LightDM
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 Unity.Notifications 1.0 as NotificationBackend
40 import Unity.Session 0.1
41 import Unity.DashCommunicator 0.1
42 
43 Item {
44  id: shell
45 
46  // this is only here to select the width / height of the window if not running fullscreen
47  property bool tablet: false
48  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
49  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
50 
51  property real edgeSize: units.gu(2)
52  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
53  property url background
54  readonly property real panelHeight: panel.panelHeight
55 
56  readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
57 
58  property bool sideStageEnabled: shell.width >= units.gu(100)
59  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
60 
61  property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
62  property int failedLoginsDelayAttempts: 7 // number of failed logins
63  property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
64 
65  property int orientation
66  readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67  onDeviceOrientationAngleChanged: {
68  if (!OrientationLock.enabled) {
69  orientation = Screen.orientation;
70  }
71  }
72  readonly property bool orientationLockEnabled: OrientationLock.enabled
73  onOrientationLockEnabledChanged: {
74  if (orientationLockEnabled) {
75  OrientationLock.savedOrientation = Screen.orientation;
76  } else {
77  orientation = Screen.orientation;
78  }
79  }
80 
81  function activateApplication(appId) {
82  if (ApplicationManager.findApplication(appId)) {
83  ApplicationManager.requestFocusApplication(appId);
84  } else {
85  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
86  ApplicationManager.startApplication(appId, execFlags);
87  }
88  }
89 
90  function setFakeActiveForApp(app) {
91  if (shell.locked) {
92  greeter.fakeActiveForApp = app
93  lockscreen.hide()
94  }
95  }
96 
97  Binding {
98  target: LauncherModel
99  property: "applicationManager"
100  value: ApplicationManager
101  }
102 
103  Component.onCompleted: {
104  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
105  if (ApplicationManager.count > 0) {
106  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
107  }
108  if (orientationLockEnabled) {
109  orientation = OrientationLock.savedOrientation;
110  }
111  }
112 
113  GSettings {
114  id: backgroundSettings
115  schema.id: "org.gnome.desktop.background"
116  }
117  property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
118  onGSettingsPictureChanged: {
119  shell.background = gSettingsPicture
120  }
121 
122  VolumeControl {
123  id: volumeControl
124  }
125 
126  DashCommunicator {
127  id: dash
128  objectName: "dashCommunicator"
129  }
130 
131  WindowKeysFilter {
132  // Handle but do not filter out volume keys
133  Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
134  Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
135 
136  Keys.onPressed: {
137  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
138  dialogs.onPowerKeyPressed();
139  event.accepted = true;
140  } else {
141  event.accepted = false;
142  }
143  }
144 
145  Keys.onReleased: {
146  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
147  dialogs.onPowerKeyReleased();
148  event.accepted = true;
149  } else {
150  event.accepted = false;
151  }
152  }
153  }
154 
155  Item {
156  id: stages
157  objectName: "stages"
158  width: parent.width
159  height: parent.height
160  visible: !ApplicationManager.empty
161 
162  Connections {
163  target: ApplicationManager
164  onFocusRequested: {
165  if (appId === "dialer-app") {
166  // Always let the dialer-app through. Either user asked
167  // for an emergency call or accepted an incoming call.
168  setFakeActiveForApp(appId)
169  } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
170  lockscreen.show();
171  }
172  greeter.hide();
173  launcher.hide();
174  }
175 
176  onFocusedApplicationIdChanged: {
177  if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
178  lockscreen.show();
179  }
180  panel.indicators.hide();
181  }
182 
183  onApplicationAdded: {
184  if (greeter.shown && appId != "unity8-dash") {
185  greeter.hide();
186  }
187  if (appId === "dialer-app") {
188  // Always let the dialer-app through. Either user asked
189  // for an emergency call or accepted an incoming call.
190  setFakeActiveForApp(appId)
191  }
192  launcher.hide();
193  }
194  }
195 
196  Loader {
197  id: applicationsDisplayLoader
198  anchors.fill: parent
199 
200  source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
201 
202  Binding {
203  target: applicationsDisplayLoader.item
204  property: "objectName"
205  value: "stage"
206  }
207  Binding {
208  target: applicationsDisplayLoader.item
209  property: "dragAreaWidth"
210  value: shell.edgeSize
211  }
212  Binding {
213  target: applicationsDisplayLoader.item
214  property: "maximizedAppTopMargin"
215  // Not just using panel.panelHeight as that changes depending on the focused app.
216  value: panel.indicators.panelHeight
217  }
218  Binding {
219  target: applicationsDisplayLoader.item
220  property: "interactive"
221  value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
222  }
223  Binding {
224  target: applicationsDisplayLoader.item
225  property: "spreadEnabled"
226  value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
227  }
228  Binding {
229  target: applicationsDisplayLoader.item
230  property: "inverseProgress"
231  value: launcher.progress
232  }
233  Binding {
234  target: applicationsDisplayLoader.item
235  property: "orientation"
236  value: shell.orientation
237  }
238  }
239  }
240 
241  InputMethod {
242  id: inputMethod
243  objectName: "inputMethod"
244  anchors { fill: parent; topMargin: panel.panelHeight }
245  z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
246  }
247 
248  Connections {
249  target: SurfaceManager
250  onSurfaceCreated: {
251  if (surface.type == MirSurfaceItem.InputMethod) {
252  inputMethod.surface = surface;
253  }
254  }
255 
256  onSurfaceDestroyed: {
257  if (inputMethod.surface == surface) {
258  inputMethod.surface = null;
259  surface.parent = null;
260  }
261  if (!surface.parent) {
262  // there's no one displaying it. delete it right away
263  surface.release();
264  }
265  }
266  }
267  Connections {
268  target: SessionManager
269  onSessionStopping: {
270  if (!session.parentSession && !session.application) {
271  // nothing is using it. delete it right away
272  session.release();
273  }
274  }
275  }
276 
277  Lockscreen {
278  id: lockscreen
279  objectName: "lockscreen"
280 
281  readonly property int backgroundTopMargin: -panel.panelHeight
282 
283  hides: [launcher, panel.indicators]
284  shown: false
285  enabled: true
286  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
287  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
288  y: panel.panelHeight
289  visible: required
290  width: parent.width
291  height: parent.height - panel.panelHeight
292  background: shell.background
293  alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
294  minPinLength: 4
295  maxPinLength: 4
296 
297  onEntered: LightDM.Greeter.respond(passphrase);
298  onCancel: greeter.show()
299  onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
300 
301  onShownChanged: if (shown) greeter.fakeActiveForApp = ""
302 
303  Component.onCompleted: {
304  if (greeter.narrowMode) {
305  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
306  }
307  }
308  }
309 
310  Connections {
311  target: LightDM.Greeter
312 
313  onShowGreeter: greeter.show()
314  onHideGreeter: greeter.login()
315 
316  onShowPrompt: {
317  if (greeter.narrowMode) {
318  if (isDefaultPrompt) {
319  if (lockscreen.alphaNumeric) {
320  lockscreen.infoText = i18n.tr("Enter your passphrase")
321  lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
322  } else {
323  lockscreen.infoText = i18n.tr("Enter your passcode")
324  lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
325  }
326  } else {
327  lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
328  lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
329  }
330 
331  lockscreen.show();
332  }
333  }
334 
335  onPromptlessChanged: {
336  if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
337  lockscreen.hide()
338  } else {
339  lockscreen.reset();
340  lockscreen.show();
341  }
342  }
343 
344  onAuthenticationComplete: {
345  if (LightDM.Greeter.authenticated) {
346  AccountsService.failedLogins = 0
347  }
348  // Else only penalize user for a failed login if they actually were
349  // prompted for a password. We do this below after the promptless
350  // early exit.
351 
352  if (LightDM.Greeter.promptless) {
353  return;
354  }
355 
356  if (LightDM.Greeter.authenticated) {
357  greeter.login();
358  } else {
359  AccountsService.failedLogins++
360  if (maxFailedLogins >= 2) { // require at least a warning
361  if (AccountsService.failedLogins === maxFailedLogins - 1) {
362  var title = lockscreen.alphaNumeric ?
363  i18n.tr("Sorry, incorrect passphrase.") :
364  i18n.tr("Sorry, incorrect passcode.")
365  var text = i18n.tr("This will be your last attempt.") + " " +
366  (lockscreen.alphaNumeric ?
367  i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
368  i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
369  lockscreen.showInfoPopup(title, text)
370  } else if (AccountsService.failedLogins >= maxFailedLogins) {
371  SystemImage.factoryReset() // Ouch!
372  }
373  }
374  if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
375  lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
376  }
377 
378  lockscreen.clear(true);
379  if (greeter.narrowMode) {
380  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
381  }
382  }
383  }
384  }
385 
386  Binding {
387  target: LightDM.Greeter
388  property: "active"
389  value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
390  }
391 
392  Rectangle {
393  anchors.fill: parent
394  color: "black"
395  opacity: greeterWrapper.showProgress * 0.8
396  }
397 
398  Item {
399  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
400  id: greeterWrapper
401  x: launcher.progress
402  y: panel.panelHeight
403  width: parent.width
404  height: parent.height - panel.panelHeight
405 
406  Behavior on x {
407  enabled: !launcher.dashSwipe
408  StandardAnimation {}
409  }
410 
411  property bool fullyShown: showProgress === 1.0
412  onFullyShownChanged: {
413  // Wait until the greeter is completely covering lockscreen before resetting it.
414  if (fullyShown && !LightDM.Greeter.authenticated) {
415  lockscreen.reset();
416  lockscreen.show();
417  }
418  }
419 
420  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
421  onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
422 
423  Greeter {
424  id: greeter
425  objectName: "greeter"
426 
427  signal sessionStarted() // helpful for tests
428 
429  property string fakeActiveForApp: ""
430 
431  available: true
432  hides: [launcher, panel.indicators]
433  shown: true
434  loadContent: required || lockscreen.required // keeps content in memory for quick show()
435 
436  defaultBackground: shell.background
437 
438  width: parent.width
439  height: parent.height
440 
441  dragHandleWidth: shell.edgeSize
442 
443  function login() {
444  enabled = false;
445  if (LightDM.Greeter.startSessionSync()) {
446  sessionStarted();
447  greeter.hide();
448  lockscreen.hide();
449  launcher.hide();
450  }
451  enabled = true;
452  }
453 
454  onShownChanged: {
455  if (shown) {
456  if (greeter.narrowMode) {
457  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
458  }
459  greeter.fakeActiveForApp = "";
460  greeter.forceActiveFocus();
461  }
462  }
463 
464  /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
465  Component.onCompleted: {
466  Connectivity.unlockAllModems()
467  } */
468 
469  onUnlocked: greeter.hide()
470  onSelected: {
471  // Update launcher items for new user
472  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
473  AccountsService.user = user;
474  LauncherModel.setUser(user);
475  }
476 
477  onTease: launcher.tease()
478 
479  Binding {
480  target: ApplicationManager
481  property: "suspended"
482  value: greeter.shown && greeterWrapper.showProgress == 1
483  }
484  }
485  }
486 
487  Connections {
488  id: powerConnection
489  target: Powerd
490 
491  onStatusChanged: {
492  if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
493  greeter.showNow()
494  }
495  }
496  }
497 
498  function showHome() {
499  if (edgeDemo.running) {
500  return
501  }
502 
503  if (LightDM.Greeter.active) {
504  if (!LightDM.Greeter.authenticated) {
505  lockscreen.show()
506  }
507  greeter.hide()
508  }
509 
510  var animate = !LightDM.Greeter.active && !stages.shown
511  dash.setCurrentScope("clickscope", animate, false)
512  ApplicationManager.requestFocusApplication("unity8-dash")
513  }
514 
515  function showDash() {
516  if (shell.locked) {
517  return;
518  }
519  if (greeter.shown) {
520  greeter.hideRight();
521  launcher.fadeOut();
522  }
523 
524  ApplicationManager.requestFocusApplication("unity8-dash")
525  launcher.fadeOut();
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: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
541  contentEnabled: edgeDemo.panelContentEnabled
542  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
543  panelHeight: units.gu(3)
544  }
545 
546  property bool topmostApplicationIsFullscreen:
547  ApplicationManager.focusedApplicationId &&
548  ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
549 
550  fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
551  || greeter.fakeActiveForApp !== ""
552  }
553 
554  Launcher {
555  id: launcher
556  objectName: "launcher"
557 
558  readonly property bool dashSwipe: progress > 0
559 
560  anchors.top: parent.top
561  anchors.bottom: parent.bottom
562  width: parent.width
563  dragAreaWidth: shell.edgeSize
564  available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
565 
566  onShowDashHome: showHome()
567  onDash: showDash()
568  onDashSwipeChanged: {
569  if (dashSwipe) {
570  dash.setCurrentScope("clickscope", false, true)
571  }
572  }
573  onLauncherApplicationSelected: {
574  if (greeter.fakeActiveForApp !== "") {
575  lockscreen.show()
576  }
577  if (!edgeDemo.running)
578  shell.activateApplication(appId)
579  }
580  onShownChanged: {
581  if (shown) {
582  panel.indicators.hide()
583  }
584  }
585  }
586 
587  Rectangle {
588  id: modalNotificationBackground
589 
590  visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
591  color: "#000000"
592  anchors.fill: parent
593  opacity: 0.9
594 
595  MouseArea {
596  anchors.fill: parent
597  }
598  }
599 
600  Notifications {
601  id: notifications
602 
603  model: NotificationBackend.Model
604  margin: units.gu(1)
605 
606  y: panel.panelHeight
607  width: parent.width
608  height: parent.height - panel.panelHeight
609 
610  states: [
611  State {
612  name: "narrow"
613  when: overlay.width <= units.gu(60)
614  AnchorChanges { target: notifications; anchors.left: parent.left }
615  },
616  State {
617  name: "wide"
618  when: overlay.width > units.gu(60)
619  AnchorChanges { target: notifications; anchors.left: undefined }
620  PropertyChanges { target: notifications; width: units.gu(38) }
621  }
622  ]
623  }
624  }
625 
626  Dialogs {
627  id: dialogs
628  anchors.fill: parent
629  z: overlay.z + 10
630  onPowerOffClicked: {
631  shutdownFadeOutRectangle.enabled = true;
632  shutdownFadeOutRectangle.visible = true;
633  shutdownFadeOut.start();
634  }
635  }
636 
637  Label {
638  id: alphaDisclaimerLabel
639  anchors.centerIn: parent
640  visible: ApplicationManager.fake ? ApplicationManager.fake : false
641  z: dialogs.z + 10
642  text: "EARLY ALPHA\nNOT READY FOR USE"
643  color: "lightgrey"
644  opacity: 0.2
645  font.weight: Font.Black
646  horizontalAlignment: Text.AlignHCenter
647  verticalAlignment: Text.AlignVCenter
648  fontSizeMode: Text.Fit
649  rotation: -45
650  scale: Math.min(parent.width, parent.height) / width
651  }
652 
653  EdgeDemo {
654  id: edgeDemo
655  z: alphaDisclaimerLabel.z + 10
656  paused: Powerd.status === Powerd.Off // Saves power
657  greeter: greeter
658  launcher: launcher
659  indicators: panel.indicators
660  stages: stages
661  }
662 
663  Connections {
664  target: SessionBroadcast
665  onShowHome: showHome()
666  }
667 
668  Rectangle {
669  id: shutdownFadeOutRectangle
670  z: edgeDemo.z + 10
671  enabled: false
672  visible: false
673  color: "black"
674  anchors.fill: parent
675  opacity: 0.0
676  NumberAnimation on opacity {
677  id: shutdownFadeOut
678  from: 0.0
679  to: 1.0
680  onStopped: {
681  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
682  DBusUnitySessionService.Shutdown();
683  }
684  }
685  }
686  }
687 
688 }