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