on 251731d help sign in
unity8/qml/Shell.qml
744 lines 24 kB history raw
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 */
17import QtQuick 2.0
18import AccountsService 0.1
19import GSettings 1.0
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Gestures 0.1
23import Unity.Launcher 0.1
24import LightDM 0.1 as LightDM
25import Powerd 0.1
26import SessionBroadcast 0.1
27import "Dash"
28import "Greeter"
29import "Launcher"
30import "Panel"
31import "Components"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
34import Unity.Session 0.1
36FocusScope {
37 id: shell
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)
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
49 property bool dashShown: dash.shown
51 property bool sideStageEnabled: shell.width >= units.gu(100)
52 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
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 }
68 Binding {
69 target: LauncherModel
70 property: "applicationManager"
71 value: ApplicationManager
72 }
74 Component.onCompleted: {
75 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76 }
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 }
87 VolumeControl {
88 id: volumeControl
89 }
91 Keys.onVolumeUpPressed: volumeControl.volumeUp()
92 Keys.onVolumeDownPressed: volumeControl.volumeDown()
94 Item {
95 id: underlayClipper
96 anchors.fill: parent
97 anchors.rightMargin: stages.overlayWidth
98 clip: stages.overlayMode && !stages.painting
100 InputFilterArea {
101 anchors.fill: parent
102 blockInput: parent.clip
103 }
105 Item {
106 id: underlay
107 objectName: "underlay"
108 anchors.fill: parent
109 anchors.rightMargin: -parent.anchors.rightMargin
111 // Whether the underlay is fully covered by opaque UI elements.
112 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
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
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
121 Rectangle {
122 anchors.fill: parent
123 color: "black"
124 opacity: dash.disappearingAnimationProgress
125 }
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 }
135 Dash {
136 id: dash
137 objectName: "dash"
139 available: !LightDM.Greeter.active
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
144 anchors {
145 fill: parent
146 topMargin: panel.panelHeight
147 }
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 }
159 // FIXME: only necessary because stages.showProgress is not animated
160 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
161 }
162 }
163 }
165 EdgeDragArea {
166 id: stagesDragHandle
167 direction: Direction.Leftwards
169 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
170 width: shell.edgeSize
172 property real progress: stages.width
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 }
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 }
194 Item {
195 id: stages
196 objectName: "stages"
197 width: parent.width
198 height: parent.height
200 x: {
201 if (shown) {
202 if (overlayMode || locked || greeter.fakeActiveForApp !== "") {
203 return 0;
204 }
205 return launcher.progress
206 } else {
207 return stagesDragHandle.progress
208 }
209 }
211 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
213 property bool shown: false
215 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
217 property bool fullyShown: x == 0
218 property bool fullyHidden: x == width
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
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 }
236 function hide() {
237 shown = false;
238 if (ApplicationManager.focusedApplicationId) {
239 ApplicationManager.unfocusCurrentApplication();
240 }
241 }
243 Connections {
244 target: ApplicationManager
246 onFocusRequested: {
247 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
248 lockscreen.show();
249 }
250 greeter.hide();
251 stages.show(true);
252 }
254 onFocusedApplicationIdChanged: {
255 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
256 lockscreen.show();
257 }
258 if (ApplicationManager.focusedApplicationId.length > 0) {
259 stages.show(false);
260 } else {
261 if (!stages.overlayMode) {
262 stages.hide();
263 }
264 }
265 }
267 onApplicationAdded: {
268 stages.show(false);
269 }
271 onApplicationRemoved: {
272 if (ApplicationManager.focusedApplicationId.length == 0) {
273 stages.hide();
274 }
275 }
276 }
278 Connections {
279 target: DBusUnitySessionService
281 function closeAllApps() {
282 while (true) {
283 var app = ApplicationManager.get(0);
284 if (app === null) {
285 break;
286 }
287 ApplicationManager.stopApplication(app.appId);
288 }
289 }
291 onLogoutRequested: {
292 // TODO: Display a dialog to ask the user to confirm.
293 DBusUnitySessionService.Logout();
294 }
296 onLogoutReady: {
297 closeAllApps();
298 Qt.quit();
299 }
300 }
302 Loader {
303 id: applicationsDisplayLoader
304 anchors.fill: parent
306 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
308 Binding {
309 target: applicationsDisplayLoader.item
310 property: "objectName"
311 value: "stage"
312 }
313 Binding {
314 target: applicationsDisplayLoader.item
315 property: "moving"
316 value: !stages.fullyShown
317 }
318 Binding {
319 target: applicationsDisplayLoader.item
320 property: "shown"
321 value: stages.shown
322 }
323 Binding {
324 target: applicationsDisplayLoader.item
325 property: "dragAreaWidth"
326 value: shell.edgeSize
327 }
328 Binding {
329 target: applicationsDisplayLoader.item
330 property: "spreadEnabled"
331 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
332 }
333 }
334 }
336 Lockscreen {
337 id: lockscreen
338 objectName: "lockscreen"
340 readonly property int backgroundTopMargin: -panel.panelHeight
342 hides: [launcher, panel.indicators]
343 shown: false
344 enabled: true
345 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
346 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
347 y: panel.panelHeight
348 x: required ? 0 : - width
349 width: parent.width
350 height: parent.height - panel.panelHeight
351 background: shell.background
352 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
353 minPinLength: 4
354 maxPinLength: 4
356 onEntered: LightDM.Greeter.respond(passphrase);
357 onCancel: greeter.show()
358 onEmergencyCall: {
359 greeter.fakeActiveForApp = "dialer-app"
360 shell.activateApplication("dialer-app")
361 lockscreen.hide()
362 }
364 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
366 Component.onCompleted: {
367 if (greeter.narrowMode) {
368 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
369 }
370 }
371 }
373 Connections {
374 target: LightDM.Greeter
376 onShowGreeter: greeter.show()
378 onShowPrompt: {
379 if (greeter.narrowMode) {
380 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
381 lockscreen.show();
382 }
383 }
385 onAuthenticationComplete: {
386 if (LightDM.Greeter.promptless) {
387 return;
388 }
389 if (LightDM.Greeter.authenticated) {
390 lockscreen.hide();
391 greeter.login();
392 } else {
393 lockscreen.clear(true);
394 if (greeter.narrowMode) {
395 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
396 }
397 }
398 }
399 }
401 Binding {
402 target: LightDM.Greeter
403 property: "active"
404 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
405 }
407 Rectangle {
408 anchors.fill: parent
409 color: "black"
410 opacity: greeterWrapper.showProgress * 0.8
411 }
413 Item {
414 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
415 id: greeterWrapper
416 x: launcher.progress
417 y: panel.panelHeight
418 width: parent.width
419 height: parent.height - panel.panelHeight
421 Behavior on x {
422 enabled: !launcher.dashSwipe
423 StandardAnimation {}
424 }
426 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
427 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
429 Greeter {
430 id: greeter
431 objectName: "greeter"
433 signal sessionStarted() // helpful for tests
435 property string fakeActiveForApp: ""
437 available: true
438 hides: [launcher, panel.indicators]
439 shown: true
441 defaultBackground: shell.background
443 width: parent.width
444 height: parent.height
446 dragHandleWidth: shell.edgeSize
448 function login() {
449 enabled = false;
450 if (LightDM.Greeter.startSessionSync()) {
451 sessionStarted();
452 greeter.hide();
453 lockscreen.hide();
454 launcher.hide();
455 }
456 enabled = true;
457 }
459 onShownChanged: {
460 if (shown) {
461 lockscreen.reset();
462 if (!LightDM.Greeter.promptless) {
463 lockscreen.show();
464 }
465 if (greeter.narrowMode) {
466 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
467 }
468 greeter.fakeActiveForApp = "";
469 greeter.forceActiveFocus();
470 }
471 }
473 onUnlocked: greeter.hide()
474 onSelected: {
475 // Update launcher items for new user
476 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
477 AccountsService.user = user;
478 LauncherModel.setUser(user);
479 }
481 onTease: launcher.tease()
483 Binding {
484 target: ApplicationManager
485 property: "suspended"
486 value: greeter.shown && greeterWrapper.showProgress == 1
487 }
488 }
489 }
491 InputFilterArea {
492 anchors.fill: parent
493 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
494 || panel.indicators.shown
495 }
497 Connections {
498 id: powerConnection
499 target: Powerd
501 onDisplayPowerStateChange: {
502 // We ignore any display-off signals when the proximity sensor
503 // is active. This usually indicates something like a phone call.
504 if (status == Powerd.Off && reason != Powerd.Proximity) {
505 greeter.showNow();
506 }
508 // No reason to chew demo CPU when user isn't watching
509 if (status == Powerd.Off) {
510 edgeDemo.paused = true;
511 } else if (status == Powerd.On) {
512 edgeDemo.paused = false;
513 }
514 }
515 }
517 function showHome() {
518 if (edgeDemo.running)
519 return
521 if (LightDM.Greeter.active) {
522 if (!LightDM.Greeter.promptless)
523 lockscreen.show()
524 greeter.hide()
525 }
527 var animate = !LightDM.Greeter.active && !stages.shown
528 dash.setCurrentScope("clickscope", animate, false)
529 stages.hide()
530 }
532 function showDash() {
533 if (LightDM.Greeter.active && !LightDM.Greeter.promptless)
534 return;
536 if (stages.shown && !stages.overlayMode) {
537 if (!stages.locked) {
538 stages.hide();
539 launcher.hide();
540 }
541 }
542 if (greeter.shown) {
543 greeter.hideRight();
544 launcher.hide();
545 }
546 }
548 function hideIndicatorMenu(delay) {
549 panel.hideIndicatorMenu(delay);
550 }
552 Item {
553 id: overlay
555 anchors.fill: parent
557 Panel {
558 id: panel
559 objectName: "panel"
560 anchors.fill: parent //because this draws indicator menus
561 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
562 indicators {
563 hides: [launcher]
564 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
565 contentEnabled: edgeDemo.panelContentEnabled
566 }
567 property string focusedAppId: ApplicationManager.focusedApplicationId
568 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
569 fullscreenMode: (focusedApplication && stages.fullscreen && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
570 searchVisible: !LightDM.Greeter.active && dash.shown && dash.searchable
572 InputFilterArea {
573 anchors {
574 top: parent.top
575 left: parent.left
576 right: parent.right
577 }
578 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
579 blockInput: true
580 }
581 }
583 InputFilterArea {
584 blockInput: launcher.shown
585 anchors {
586 top: parent.top
587 bottom: parent.bottom
588 left: parent.left
589 }
590 width: launcher.width
591 }
593 Launcher {
594 id: launcher
595 objectName: "launcher"
597 readonly property bool dashSwipe: progress > 0
599 anchors.top: parent.top
600 anchors.bottom: parent.bottom
601 width: parent.width
602 dragAreaWidth: shell.edgeSize
603 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
605 onShowDashHome: showHome()
606 onDash: showDash()
607 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
608 onLauncherApplicationSelected: {
609 if (greeter.fakeActiveForApp !== "")
610 lockscreen.show()
611 if (!edgeDemo.running)
612 shell.activateApplication(appId)
613 }
614 onShownChanged: {
615 if (shown) {
616 panel.indicators.hide()
617 }
618 }
619 }
621 Rectangle {
622 id: modalNotificationBackground
624 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
625 color: "#000000"
626 anchors.fill: parent
627 opacity: 0.5
629 MouseArea {
630 anchors.fill: parent
631 }
633 InputFilterArea {
634 anchors.fill: parent
635 blockInput: modalNotificationBackground.visible
636 }
637 }
639 Notifications {
640 id: notifications
642 model: NotificationBackend.Model
643 margin: units.gu(1)
645 anchors {
646 top: parent.top
647 right: parent.right
648 bottom: parent.bottom
649 topMargin: panel.panelHeight
650 }
651 states: [
652 State {
653 name: "narrow"
654 when: overlay.width <= units.gu(60)
655 AnchorChanges { target: notifications; anchors.left: parent.left }
656 },
657 State {
658 name: "wide"
659 when: overlay.width > units.gu(60)
660 AnchorChanges { target: notifications; anchors.left: undefined }
661 PropertyChanges { target: notifications; width: units.gu(38) }
662 }
663 ]
665 InputFilterArea {
666 anchors { left: parent.left; right: parent.right }
667 height: parent.contentHeight
668 blockInput: height > 0
669 }
670 }
671 }
673 focus: true
674 onFocusChanged: if (!focus) forceActiveFocus();
676 InputFilterArea {
677 anchors {
678 top: parent.top
679 bottom: parent.bottom
680 left: parent.left
681 }
682 width: shell.edgeSize
683 blockInput: true
684 }
686 InputFilterArea {
687 anchors {
688 top: parent.top
689 bottom: parent.bottom
690 right: parent.right
691 }
692 width: shell.edgeSize
693 blockInput: true
694 }
696 Binding {
697 target: i18n
698 property: "domain"
699 value: "unity8"
700 }
702 OSKController {
703 anchors.topMargin: panel.panelHeight
704 anchors.fill: parent // as needs to know the geometry of the shell
705 }
707 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
708 MouseArea {
709 anchors.bottom: parent.bottom
710 anchors.left: parent.left
711 anchors.right: parent.right
712 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
714 enabled: ApplicationManager.keyboardVisible
715 }
717 Label {
718 anchors.centerIn: parent
719 visible: ApplicationManager.fake ? ApplicationManager.fake : false
720 text: "EARLY ALPHA\nNOT READY FOR USE"
721 color: "lightgrey"
722 opacity: 0.2
723 font.weight: Font.Black
724 horizontalAlignment: Text.AlignHCenter
725 verticalAlignment: Text.AlignVCenter
726 fontSizeMode: Text.Fit
727 rotation: -45
728 scale: Math.min(parent.width, parent.height) / width
729 }
731 EdgeDemo {
732 id: edgeDemo
733 greeter: greeter
734 launcher: launcher
735 dash: dash
736 indicators: panel.indicators
737 underlay: underlay
738 }
740 Connections {
741 target: SessionBroadcast
742 onShowHome: showHome()
743 }