Unity 8
 All Classes Functions Properties
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 AccountsService 0.1
19 import GSettings 1.0
20 import Unity.Application 0.1
21 import Ubuntu.Components 0.1
22 import Ubuntu.Gestures 0.1
23 import Unity.Launcher 0.1
24 import LightDM 0.1 as LightDM
25 import Powerd 0.1
26 import SessionBroadcast 0.1
27 import "Dash"
28 import "Greeter"
29 import "Launcher"
30 import "Panel"
31 import "Components"
32 import "Notifications"
33 import Unity.Notifications 1.0 as NotificationBackend
34 import Unity.Session 0.1
35 
36 FocusScope {
37  id: shell
38 
39  // this is only here to select the width / height of the window if not running fullscreen
40  property bool tablet: false
41  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
42  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
43 
44  property real edgeSize: units.gu(2)
45  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
46  property url background
47  readonly property real panelHeight: panel.panelHeight
48 
49  property bool dashShown: dash.shown
50 
51  property bool sideStageEnabled: shell.width >= units.gu(100)
52  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
53 
54  function activateApplication(appId) {
55  if (ApplicationManager.findApplication(appId)) {
56  ApplicationManager.requestFocusApplication(appId);
57  stages.show(true);
58  if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
59  applicationsDisplayLoader.item.select(appId);
60  }
61  } else {
62  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
63  ApplicationManager.startApplication(appId, execFlags);
64  stages.show(false);
65  }
66  }
67 
68  Binding {
69  target: LauncherModel
70  property: "applicationManager"
71  value: ApplicationManager
72  }
73 
74  Component.onCompleted: {
75  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76  }
77 
78  GSettings {
79  id: backgroundSettings
80  schema.id: "org.gnome.desktop.background"
81  }
82  property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
83  onGSettingsPictureChanged: {
84  shell.background = gSettingsPicture
85  }
86 
87  VolumeControl {
88  id: volumeControl
89  }
90 
91  Keys.onVolumeUpPressed: volumeControl.volumeUp()
92  Keys.onVolumeDownPressed: volumeControl.volumeDown()
93 
94  Item {
95  id: underlayClipper
96  anchors.fill: parent
97  anchors.rightMargin: stages.overlayWidth
98  clip: stages.overlayMode && !stages.painting
99 
100  InputFilterArea {
101  anchors.fill: parent
102  blockInput: parent.clip
103  }
104 
105  Item {
106  id: underlay
107  objectName: "underlay"
108  anchors.fill: parent
109  anchors.rightMargin: -parent.anchors.rightMargin
110 
111  // Whether the underlay is fully covered by opaque UI elements.
112  property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
113 
114  // Whether the user should see the topmost application surface (if there's one at all).
115  readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
116 
117  // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
118  // through the translucent parts of the shell surface.
119  visible: !fullyCovered && !applicationSurfaceShouldBeSeen
120 
121  Rectangle {
122  anchors.fill: parent
123  color: "black"
124  opacity: dash.disappearingAnimationProgress
125  }
126 
127  Image {
128  anchors.fill: dash
129  source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
130  fillMode: Image.PreserveAspectCrop
131  horizontalAlignment: Image.AlignRight
132  verticalAlignment: Image.AlignTop
133  }
134 
135  Dash {
136  id: dash
137  objectName: "dash"
138 
139  available: !greeter.shown && !lockscreen.shown
140  hides: [stages, launcher, panel.indicators]
141  shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
142  enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
143 
144  anchors {
145  fill: parent
146  topMargin: panel.panelHeight
147  }
148 
149  contentScale: 1.0 - 0.2 * disappearingAnimationProgress
150  opacity: 1.0 - disappearingAnimationProgress
151  property real disappearingAnimationProgress: {
152  if (stages.overlayMode) {
153  return 0;
154  } else {
155  return stages.showProgress
156  }
157  }
158 
159  // FIXME: only necessary because stages.showProgress is not animated
160  Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
161  }
162  }
163  }
164 
165  EdgeDragArea {
166  id: stagesDragHandle
167  direction: Direction.Leftwards
168 
169  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
170  width: shell.edgeSize
171 
172  property real progress: stages.width
173 
174  onTouchXChanged: {
175  if (status == DirectionalDragArea.Recognized) {
176  if (ApplicationManager.count == 0) {
177  progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
178  } else {
179  progress = stages.width - stagesDragHandle.width + touchX
180  }
181  }
182  }
183 
184  onDraggingChanged: {
185  if (!dragging) {
186  if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
187  stages.show(true)
188  }
189  stagesDragHandle.progress = stages.width;
190  }
191  }
192  }
193 
194  Item {
195  id: stages
196  objectName: "stages"
197  width: parent.width
198  height: parent.height
199 
200  x: {
201  if (shown) {
202  if (overlayMode || locked) {
203  return 0;
204  }
205  return launcher.progress
206  } else {
207  return stagesDragHandle.progress
208  }
209  }
210 
211  Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
212 
213  property bool shown: false
214 
215  property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
216 
217  property bool fullyShown: x == 0
218  property bool fullyHidden: x == width
219 
220  property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
221  property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
222  property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
223  property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
224  property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
225 
226  function show(focusApp) {
227  shown = true;
228  panel.indicators.hide();
229  edgeDemo.stopDemo();
230  greeter.hide();
231  if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
232  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
233  }
234  }
235 
236  function hide() {
237  shown = false;
238  if (ApplicationManager.focusedApplicationId) {
239  ApplicationManager.unfocusCurrentApplication();
240  }
241  }
242 
243  Connections {
244  target: ApplicationManager
245 
246  onFocusRequested: {
247  stages.show(true);
248  }
249 
250  onFocusedApplicationIdChanged: {
251  if (ApplicationManager.focusedApplicationId.length > 0) {
252  stages.show(false);
253  } else {
254  if (!stages.overlayMode) {
255  stages.hide();
256  }
257  }
258  }
259 
260  onApplicationAdded: {
261  stages.show(false);
262  }
263 
264  onApplicationRemoved: {
265  if (ApplicationManager.focusedApplicationId.length == 0) {
266  stages.hide();
267  }
268  }
269  }
270 
271  Connections {
273 
274  function closeAllApps() {
275  while (true) {
276  var app = ApplicationManager.get(0);
277  if (app === null) {
278  break;
279  }
280  ApplicationManager.stopApplication(app.appId);
281  }
282  }
283 
284  onLogoutRequested: {
285  // TODO: Display a dialog to ask the user to confirm.
287  }
288 
289  onLogoutReady: {
290  closeAllApps();
291  Qt.quit();
292  }
293  }
294 
295  Loader {
296  id: applicationsDisplayLoader
297  anchors.fill: parent
298 
299  source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
300 
301  Binding {
302  target: applicationsDisplayLoader.item
303  property: "moving"
304  value: !stages.fullyShown
305  }
306  Binding {
307  target: applicationsDisplayLoader.item
308  property: "shown"
309  value: stages.shown
310  }
311  Binding {
312  target: applicationsDisplayLoader.item
313  property: "dragAreaWidth"
314  value: shell.edgeSize
315  }
316  }
317  }
318 
319  Lockscreen {
320  id: lockscreen
321  objectName: "lockscreen"
322 
323  readonly property int backgroundTopMargin: -panel.panelHeight
324 
325  hides: [launcher, panel.indicators]
326  shown: false
327  enabled: true
328  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
329  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
330  y: panel.panelHeight
331  x: required ? 0 : - width
332  width: parent.width
333  height: parent.height - panel.panelHeight
334  background: shell.background
335  minPinLength: 4
336  maxPinLength: 4
337 
338  onEntered: LightDM.Greeter.respond(passphrase);
339  onCancel: greeter.show()
340 
341  Component.onCompleted: {
342  if (LightDM.Users.count == 1) {
343  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
344  }
345  }
346  }
347 
348  Connections {
349  target: LightDM.Greeter
350 
351  onShowPrompt: {
352  if (LightDM.Users.count == 1) {
353  // TODO: There's no better way for now to determine if its a PIN or a passphrase.
354  if (text == "PIN") {
355  lockscreen.alphaNumeric = false
356  } else {
357  lockscreen.alphaNumeric = true
358  }
359  lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
360  lockscreen.show();
361  }
362  }
363 
364  onAuthenticationComplete: {
365  if (LightDM.Greeter.promptless) {
366  return;
367  }
368  if (LightDM.Greeter.authenticated) {
369  lockscreen.hide();
370  } else {
371  lockscreen.clear(true);
372  }
373  }
374  }
375 
376  Rectangle {
377  anchors.fill: parent
378  color: "black"
379  opacity: greeterWrapper.showProgress * 0.8
380  }
381 
382  Item {
383  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
384  id: greeterWrapper
385  x: launcher.progress
386  y: panel.panelHeight
387  width: parent.width
388  height: parent.height - panel.panelHeight
389 
390  Behavior on x {
391  enabled: !launcher.dashSwipe
392  StandardAnimation {}
393  }
394 
395  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
396 
397  Greeter {
398  id: greeter
399  objectName: "greeter"
400 
401  available: true
402  hides: [launcher, panel.indicators]
403  shown: true
404 
405  defaultBackground: shell.background
406 
407  width: parent.width
408  height: parent.height
409 
410  dragHandleWidth: shell.edgeSize
411 
412  onShownChanged: {
413  if (shown) {
414  lockscreen.reset();
415  // If there is only one user, we start authenticating with that one here.
416  // If there are more users, the Greeter will handle that
417  if (LightDM.Users.count == 1) {
418  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
419  }
420  greeter.forceActiveFocus();
421  }
422  }
423 
424  onUnlocked: greeter.hide()
425  onSelected: {
426  // Update launcher items for new user
427  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
428  AccountsService.user = user;
429  LauncherModel.setUser(user);
430  }
431 
432  onTease: launcher.tease()
433 
434  Binding {
435  target: ApplicationManager
436  property: "suspended"
437  value: greeter.shown && greeterWrapper.showProgress == 1
438  }
439  }
440  }
441 
442  InputFilterArea {
443  anchors.fill: parent
444  blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
445  || panel.indicators.shown
446  }
447 
448  Connections {
449  id: powerConnection
450  target: Powerd
451 
452  onDisplayPowerStateChange: {
453  // We ignore any display-off signals when the proximity sensor
454  // is active. This usually indicates something like a phone call.
455  if (status == Powerd.Off && reason != Powerd.Proximity) {
456  greeter.showNow();
457  }
458 
459  // No reason to chew demo CPU when user isn't watching
460  if (status == Powerd.Off) {
461  edgeDemo.paused = true;
462  } else if (status == Powerd.On) {
463  edgeDemo.paused = false;
464  }
465  }
466  }
467 
468  function showHome() {
469  var animate = !greeter.shown && !stages.shown
470  greeter.hide()
471  dash.setCurrentScope("clickscope", animate, false)
472  stages.hide()
473  }
474 
475  function hideIndicatorMenu(delay) {
476  panel.hideIndicatorMenu(delay);
477  }
478 
479  Item {
480  id: overlay
481 
482  anchors.fill: parent
483 
484  Panel {
485  id: panel
486  anchors.fill: parent //because this draws indicator menus
487  indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
488  indicators {
489  hides: [launcher]
490  available: edgeDemo.panelEnabled
491  contentEnabled: edgeDemo.panelContentEnabled
492  }
493  property string focusedAppId: ApplicationManager.focusedApplicationId
494  property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
495  fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
496  searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
497 
498  InputFilterArea {
499  anchors {
500  top: parent.top
501  left: parent.left
502  right: parent.right
503  }
504  height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
505  blockInput: true
506  }
507  }
508 
509  InputFilterArea {
510  blockInput: launcher.shown
511  anchors {
512  top: parent.top
513  bottom: parent.bottom
514  left: parent.left
515  }
516  width: launcher.width
517  }
518 
519  Launcher {
520  id: launcher
521 
522  readonly property bool dashSwipe: progress > 0
523 
524  anchors.top: parent.top
525  anchors.bottom: parent.bottom
526  width: parent.width
527  dragAreaWidth: shell.edgeSize
528  available: edgeDemo.launcherEnabled
529 
530  onShowDashHome: {
531  if (edgeDemo.running)
532  return;
533 
534  showHome()
535  }
536  onDash: {
537  if (stages.shown && !stages.overlayMode) {
538  if (!stages.locked) {
539  stages.hide();
540  launcher.hide();
541  }
542  }
543  if (greeter.shown) {
544  greeter.hideRight();
545  launcher.hide();
546  }
547  }
548  onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
549  onLauncherApplicationSelected: {
550  if (!edgeDemo.running)
551  shell.activateApplication(appId)
552  }
553  onShownChanged: {
554  if (shown) {
555  panel.indicators.hide()
556  }
557  }
558  }
559 
560  Rectangle {
561  id: modalNotificationBackground
562 
563  visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
564  color: "#000000"
565  anchors.fill: parent
566  opacity: 0.5
567 
568  MouseArea {
569  anchors.fill: parent
570  }
571 
572  InputFilterArea {
573  anchors.fill: parent
574  blockInput: modalNotificationBackground.visible
575  }
576  }
577 
578  Notifications {
579  id: notifications
580 
581  model: NotificationBackend.Model
582  margin: units.gu(1)
583 
584  anchors {
585  top: parent.top
586  right: parent.right
587  bottom: parent.bottom
588  topMargin: panel.panelHeight
589  }
590  states: [
591  State {
592  name: "narrow"
593  when: overlay.width <= units.gu(60)
594  AnchorChanges { target: notifications; anchors.left: parent.left }
595  },
596  State {
597  name: "wide"
598  when: overlay.width > units.gu(60)
599  AnchorChanges { target: notifications; anchors.left: undefined }
600  PropertyChanges { target: notifications; width: units.gu(38) }
601  }
602  ]
603 
604  InputFilterArea {
605  anchors { left: parent.left; right: parent.right }
606  height: parent.contentHeight
607  blockInput: height > 0
608  }
609  }
610  }
611 
612  focus: true
613  onFocusChanged: if (!focus) forceActiveFocus();
614 
615  InputFilterArea {
616  anchors {
617  top: parent.top
618  bottom: parent.bottom
619  left: parent.left
620  }
621  width: shell.edgeSize
622  blockInput: true
623  }
624 
625  InputFilterArea {
626  anchors {
627  top: parent.top
628  bottom: parent.bottom
629  right: parent.right
630  }
631  width: shell.edgeSize
632  blockInput: true
633  }
634 
635  Binding {
636  target: i18n
637  property: "domain"
638  value: "unity8"
639  }
640 
641  OSKController {
642  anchors.topMargin: panel.panelHeight
643  anchors.fill: parent // as needs to know the geometry of the shell
644  }
645 
646  //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
647  MouseArea {
648  anchors.bottom: parent.bottom
649  anchors.left: parent.left
650  anchors.right: parent.right
651  height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
652 
653  enabled: ApplicationManager.keyboardVisible
654  }
655 
656  Label {
657  anchors.centerIn: parent
658  visible: ApplicationManager.fake ? ApplicationManager.fake : false
659  text: "EARLY ALPHA\nNOT READY FOR USE"
660  color: "lightgrey"
661  opacity: 0.2
662  font.weight: Font.Black
663  horizontalAlignment: Text.AlignHCenter
664  verticalAlignment: Text.AlignVCenter
665  fontSizeMode: Text.Fit
666  rotation: -45
667  scale: Math.min(parent.width, parent.height) / width
668  }
669 
670  EdgeDemo {
671  id: edgeDemo
672  greeter: greeter
673  launcher: launcher
674  dash: dash
675  indicators: panel.indicators
676  underlay: underlay
677  }
678 
679  Connections {
680  target: SessionBroadcast
681  onShowHome: showHome()
682  }
683 }