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 failedLoginsDelayMinutes: 5 // minutes 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  Binding {
132  target: ApplicationManager
133  property: "forceDashActive"
134  value: launcher.shown || launcher.dashSwipe
135  }
136 
137 
138  WindowKeysFilter {
139  // Handle but do not filter out volume keys
140  Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
141  Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
142 
143  Keys.onPressed: {
144  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
145  dialogs.onPowerKeyPressed();
146  event.accepted = true;
147  } else {
148  event.accepted = false;
149  }
150  }
151 
152  Keys.onReleased: {
153  if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
154  dialogs.onPowerKeyReleased();
155  event.accepted = true;
156  } else {
157  event.accepted = false;
158  }
159  }
160  }
161 
162  Item {
163  id: stages
164  objectName: "stages"
165  width: parent.width
166  height: parent.height
167  visible: !ApplicationManager.empty
168 
169  Connections {
170  target: ApplicationManager
171  onFocusRequested: {
172  if (appId === "dialer-app") {
173  // Always let the dialer-app through. Either user asked
174  // for an emergency call or accepted an incoming call.
175  setFakeActiveForApp(appId)
176  } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
177  lockscreen.show();
178  }
179  greeter.hide();
180  }
181 
182  onFocusedApplicationIdChanged: {
183  if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
184  lockscreen.show();
185  }
186  panel.indicators.hide();
187  }
188 
189  onApplicationAdded: {
190  if (greeter.shown && appId != "unity8-dash") {
191  greeter.hide();
192  }
193  if (appId === "dialer-app") {
194  // Always let the dialer-app through. Either user asked
195  // for an emergency call or accepted an incoming call.
196  setFakeActiveForApp(appId)
197  }
198  launcher.hide();
199  }
200  }
201 
202  Loader {
203  id: applicationsDisplayLoader
204  anchors.fill: parent
205 
206  source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
207 
208  Binding {
209  target: applicationsDisplayLoader.item
210  property: "objectName"
211  value: "stage"
212  }
213  Binding {
214  target: applicationsDisplayLoader.item
215  property: "dragAreaWidth"
216  value: shell.edgeSize
217  }
218  Binding {
219  target: applicationsDisplayLoader.item
220  property: "maximizedAppTopMargin"
221  // Not just using panel.panelHeight as that changes depending on the focused app.
222  value: panel.indicators.panelHeight
223  }
224  Binding {
225  target: applicationsDisplayLoader.item
226  property: "interactive"
227  value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
228  }
229  Binding {
230  target: applicationsDisplayLoader.item
231  property: "spreadEnabled"
232  value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
233  }
234  Binding {
235  target: applicationsDisplayLoader.item
236  property: "inverseProgress"
237  value: launcher.progress
238  }
239  Binding {
240  target: applicationsDisplayLoader.item
241  property: "orientation"
242  value: shell.orientation
243  }
244  }
245  }
246 
247  InputMethod {
248  id: inputMethod
249  objectName: "inputMethod"
250  anchors { fill: parent; topMargin: panel.panelHeight }
251  z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
252  }
253 
254  Connections {
255  target: SurfaceManager
256  onSurfaceCreated: {
257  if (surface.type == MirSurfaceItem.InputMethod) {
258  inputMethod.surface = surface;
259  }
260  }
261 
262  onSurfaceDestroyed: {
263  if (inputMethod.surface == surface) {
264  inputMethod.surface = null;
265  surface.parent = null;
266  }
267  if (!surface.parent) {
268  // there's no one displaying it. delete it right away
269  surface.release();
270  }
271  }
272  }
273  Connections {
274  target: SessionManager
275  onSessionStopping: {
276  if (!session.parentSession && !session.application) {
277  // nothing is using it. delete it right away
278  session.release();
279  }
280  }
281  }
282 
283  Lockscreen {
284  id: lockscreen
285  objectName: "lockscreen"
286 
287  hides: [launcher, panel.indicators]
288  shown: false
289  enabled: true
290  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
291  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
292  y: panel.panelHeight
293  visible: required
294  width: parent.width
295  height: parent.height - panel.panelHeight
296  background: shell.background
297  alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
298  minPinLength: 4
299  maxPinLength: 4
300 
301  // FIXME: We *should* show emergency dialer if there is a SIM present,
302  // regardless of whether the side stage is enabled. But right now,
303  // the assumption is that narrow screens are phones which have SIMs
304  // and wider screens are tablets which don't. When we do allow this
305  // on devices with a side stage and a SIM, work should be done to
306  // ensure that the main stage is disabled while the dialer is present
307  // in the side stage.
308  showEmergencyCallButton: !shell.sideStageEnabled
309 
310  onEntered: LightDM.Greeter.respond(passphrase);
311  onCancel: greeter.show()
312  onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
313 
314  onShownChanged: if (shown) greeter.fakeActiveForApp = ""
315 
316  Timer {
317  id: forcedDelayTimer
318  interval: 1000 * 60
319  onTriggered: {
320  if (lockscreen.delayMinutes > 0) {
321  lockscreen.delayMinutes -= 1
322  if (lockscreen.delayMinutes > 0) {
323  start() // go again
324  }
325  }
326  }
327  }
328 
329  Component.onCompleted: {
330  if (greeter.narrowMode) {
331  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
332  }
333  }
334  }
335 
336  Connections {
337  target: LightDM.Greeter
338 
339  onShowGreeter: greeter.show()
340  onHideGreeter: greeter.login()
341 
342  onShowPrompt: {
343  if (greeter.narrowMode) {
344  if (isDefaultPrompt) {
345  if (lockscreen.alphaNumeric) {
346  lockscreen.infoText = i18n.tr("Enter passphrase")
347  lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
348  i18n.tr("Please re-enter")
349  } else {
350  lockscreen.infoText = i18n.tr("Enter passcode")
351  lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
352  }
353  } else {
354  lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
355  lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
356  }
357 
358  lockscreen.show();
359  }
360  }
361 
362  onPromptlessChanged: {
363  if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
364  lockscreen.hide()
365  } else {
366  lockscreen.reset();
367  lockscreen.show();
368  }
369  }
370 
371  onAuthenticationComplete: {
372  if (LightDM.Greeter.authenticated) {
373  AccountsService.failedLogins = 0
374  }
375  // Else only penalize user for a failed login if they actually were
376  // prompted for a password. We do this below after the promptless
377  // early exit.
378 
379  if (LightDM.Greeter.promptless) {
380  return;
381  }
382 
383  if (LightDM.Greeter.authenticated) {
384  greeter.login();
385  } else {
386  AccountsService.failedLogins++
387  if (maxFailedLogins >= 2) { // require at least a warning
388  if (AccountsService.failedLogins === maxFailedLogins - 1) {
389  var title = lockscreen.alphaNumeric ?
390  i18n.tr("Sorry, incorrect passphrase.") :
391  i18n.tr("Sorry, incorrect passcode.")
392  var text = i18n.tr("This will be your last attempt.") + " " +
393  (lockscreen.alphaNumeric ?
394  i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
395  i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
396  lockscreen.showInfoPopup(title, text)
397  } else if (AccountsService.failedLogins >= maxFailedLogins) {
398  SystemImage.factoryReset() // Ouch!
399  }
400  }
401  if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
402  lockscreen.delayMinutes = failedLoginsDelayMinutes
403  forcedDelayTimer.start()
404  }
405 
406  lockscreen.clear(true);
407  if (greeter.narrowMode) {
408  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
409  }
410  }
411  }
412  }
413 
414  Binding {
415  target: LightDM.Greeter
416  property: "active"
417  value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
418  }
419 
420  Rectangle {
421  anchors.fill: parent
422  color: "black"
423  opacity: greeterWrapper.showProgress * 0.8
424  }
425 
426  Item {
427  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
428  id: greeterWrapper
429  x: launcher.progress
430  y: panel.panelHeight
431  width: parent.width
432  height: parent.height - panel.panelHeight
433 
434  Behavior on x {
435  enabled: !launcher.dashSwipe
436  StandardAnimation {}
437  }
438 
439  property bool fullyShown: showProgress === 1.0
440  onFullyShownChanged: {
441  // Wait until the greeter is completely covering lockscreen before resetting it.
442  if (fullyShown && !LightDM.Greeter.authenticated) {
443  lockscreen.reset();
444  lockscreen.show();
445  }
446  }
447 
448  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
449  onShowProgressChanged: {
450  if (showProgress === 0) {
451  if (LightDM.Greeter.authenticated) {
452  greeter.login()
453  } else if (greeter.narrowMode) {
454  lockscreen.clear(false) // to reset focus if necessary
455  }
456  }
457  }
458 
459  Greeter {
460  id: greeter
461  objectName: "greeter"
462 
463  signal sessionStarted() // helpful for tests
464 
465  property string fakeActiveForApp: ""
466 
467  available: true
468  hides: [launcher, panel.indicators]
469  shown: true
470  loadContent: required || lockscreen.required // keeps content in memory for quick show()
471 
472  defaultBackground: shell.background
473 
474  width: parent.width
475  height: parent.height
476 
477  dragHandleWidth: shell.edgeSize
478 
479  function login() {
480  enabled = false;
481  if (LightDM.Greeter.startSessionSync()) {
482  sessionStarted();
483  greeter.hide();
484  lockscreen.hide();
485  launcher.hide();
486  }
487  enabled = true;
488  }
489 
490  onShownChanged: {
491  if (shown) {
492  if (greeter.narrowMode) {
493  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
494  }
495  greeter.fakeActiveForApp = "";
496  greeter.forceActiveFocus();
497  }
498  }
499 
500  /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
501  Component.onCompleted: {
502  Connectivity.unlockAllModems()
503  } */
504 
505  onUnlocked: greeter.hide()
506  onSelected: {
507  // Update launcher items for new user
508  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
509  AccountsService.user = user;
510  LauncherModel.setUser(user);
511  }
512 
513  onTease: launcher.tease()
514 
515  Binding {
516  target: ApplicationManager
517  property: "suspended"
518  value: greeter.shown && greeterWrapper.showProgress == 1
519  }
520  }
521  }
522 
523  Connections {
524  id: powerConnection
525  target: Powerd
526 
527  onStatusChanged: {
528  if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
529  greeter.showNow()
530  }
531  }
532  }
533 
534  function showHome() {
535  if (edgeDemo.running) {
536  return
537  }
538 
539  if (LightDM.Greeter.active) {
540  if (!LightDM.Greeter.authenticated) {
541  lockscreen.show()
542  }
543  greeter.hide()
544  }
545 
546  var animate = !LightDM.Greeter.active && !stages.shown
547  dash.setCurrentScope("clickscope", animate, false)
548  ApplicationManager.requestFocusApplication("unity8-dash")
549  }
550 
551  function showDash() {
552  if (greeter.fakeActiveForApp !== "") { // just in case user gets here
553  return
554  }
555 
556  if (greeter.shown) {
557  greeter.hideRight();
558  launcher.fadeOut();
559  }
560 
561  if (ApplicationManager.focusedApplicationId != "unity8-dash") {
562  ApplicationManager.requestFocusApplication("unity8-dash")
563  launcher.fadeOut();
564  }
565  }
566 
567  Item {
568  id: overlay
569  z: 10
570 
571  anchors.fill: parent
572 
573  Panel {
574  id: panel
575  objectName: "panel"
576  anchors.fill: parent //because this draws indicator menus
577  indicators {
578  hides: [launcher]
579  available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
580  contentEnabled: edgeDemo.panelContentEnabled
581  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
582  panelHeight: units.gu(3)
583  }
584 
585  property bool topmostApplicationIsFullscreen:
586  ApplicationManager.focusedApplicationId &&
587  ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
588 
589  fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
590  || greeter.fakeActiveForApp !== ""
591  }
592 
593  Launcher {
594  id: launcher
595  objectName: "launcher"
596 
597  readonly property bool dashSwipe: progress > 0
598 
599  anchors.top: parent.top
600  anchors.bottom: parent.bottom
601  width: parent.width
602  dragAreaWidth: shell.edgeSize
603  available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
604 
605  onShowDashHome: showHome()
606  onDash: showDash()
607  onDashSwipeChanged: {
608  if (dashSwipe) {
609  dash.setCurrentScope("clickscope", false, true)
610  }
611  }
612  onLauncherApplicationSelected: {
613  if (greeter.fakeActiveForApp !== "") {
614  lockscreen.show()
615  }
616  if (!edgeDemo.running)
617  shell.activateApplication(appId)
618  }
619  onShownChanged: {
620  if (shown) {
621  panel.indicators.hide()
622  }
623  }
624  }
625 
626  Rectangle {
627  id: modalNotificationBackground
628 
629  visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
630  color: "#000000"
631  anchors.fill: parent
632  opacity: 0.9
633 
634  MouseArea {
635  anchors.fill: parent
636  }
637  }
638 
639  Notifications {
640  id: notifications
641 
642  model: NotificationBackend.Model
643  margin: units.gu(1)
644 
645  y: panel.panelHeight
646  width: parent.width
647  height: parent.height - panel.panelHeight
648 
649  states: [
650  State {
651  name: "narrow"
652  when: overlay.width <= units.gu(60)
653  AnchorChanges { target: notifications; anchors.left: parent.left }
654  },
655  State {
656  name: "wide"
657  when: overlay.width > units.gu(60)
658  AnchorChanges { target: notifications; anchors.left: undefined }
659  PropertyChanges { target: notifications; width: units.gu(38) }
660  }
661  ]
662  }
663  }
664 
665  Dialogs {
666  id: dialogs
667  anchors.fill: parent
668  z: overlay.z + 10
669  onPowerOffClicked: {
670  shutdownFadeOutRectangle.enabled = true;
671  shutdownFadeOutRectangle.visible = true;
672  shutdownFadeOut.start();
673  }
674  }
675 
676  Label {
677  id: alphaDisclaimerLabel
678  anchors.centerIn: parent
679  visible: ApplicationManager.fake ? ApplicationManager.fake : false
680  z: dialogs.z + 10
681  text: "EARLY ALPHA\nNOT READY FOR USE"
682  color: "lightgrey"
683  opacity: 0.2
684  font.weight: Font.Black
685  horizontalAlignment: Text.AlignHCenter
686  verticalAlignment: Text.AlignVCenter
687  fontSizeMode: Text.Fit
688  rotation: -45
689  scale: Math.min(parent.width, parent.height) / width
690  }
691 
692  EdgeDemo {
693  id: edgeDemo
694  z: alphaDisclaimerLabel.z + 10
695  paused: Powerd.status === Powerd.Off // Saves power
696  greeter: greeter
697  launcher: launcher
698  indicators: panel.indicators
699  stages: stages
700  }
701 
702  Connections {
703  target: SessionBroadcast
704  onShowHome: showHome()
705  }
706 
707  Rectangle {
708  id: shutdownFadeOutRectangle
709  z: edgeDemo.z + 10
710  enabled: false
711  visible: false
712  color: "black"
713  anchors.fill: parent
714  opacity: 0.0
715  NumberAnimation on opacity {
716  id: shutdownFadeOut
717  from: 0.0
718  to: 1.0
719  onStopped: {
720  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
721  DBusUnitySessionService.Shutdown();
722  }
723  }
724  }
725  }
726 
727 }