on ac27ca7 help sign in
unity8/qml/Shell.qml
720 lines 23 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 Ubuntu.Telephony 0.1 as Telephony
24import Unity.Launcher 0.1
25import Utils 0.1
26import LightDM 0.1 as LightDM
27import Powerd 0.1
28import SessionBroadcast 0.1
29import "Dash"
30import "Greeter"
31import "Launcher"
32import "Panel"
33import "Components"
34import "Notifications"
35import "Stages"
36import Unity.Notifications 1.0 as NotificationBackend
37import Unity.Session 0.1
39Item {
40 id: shell
42 // this is only here to select the width / height of the window if not running fullscreen
43 property bool tablet: false
44 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
45 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
47 property real edgeSize: units.gu(2)
48 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
49 property url background
50 readonly property real panelHeight: panel.panelHeight
52 property bool dashShown: dash.shown && dash.available && underlay.visible
54 property bool sideStageEnabled: shell.width >= units.gu(100)
55 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
57 function activateApplication(appId) {
58 if (ApplicationManager.findApplication(appId)) {
59 ApplicationManager.requestFocusApplication(appId);
60 } else {
61 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62 ApplicationManager.startApplication(appId, execFlags);
63 stages.show();
64 }
65 }
67 Binding {
68 target: LauncherModel
69 property: "applicationManager"
70 value: ApplicationManager
71 }
73 Component.onCompleted: {
74 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75 }
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 }
86 VolumeControl {
87 id: volumeControl
88 }
90 WindowKeysFilter {
91 // Handle but do not filter out volume keys
92 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
93 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
95 Keys.onPressed: {
96 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
97 dialogs.onPowerKeyPressed();
98 event.accepted = true;
99 } else {
100 event.accepted = false;
101 }
102 }
104 Keys.onReleased: {
105 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
106 dialogs.onPowerKeyReleased();
107 event.accepted = true;
108 } else {
109 event.accepted = false;
110 }
111 }
112 }
114 Item {
115 id: underlay
116 objectName: "underlay"
117 anchors.fill: parent
119 // Whether the underlay is fully covered by opaque UI elements.
120 property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
121 || stages.fullyShown || greeterWrapper.fullyShown
122 visible: !fullyCovered
124 Image {
125 anchors.fill: dash
126 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
127 fillMode: Image.PreserveAspectCrop
128 horizontalAlignment: Image.AlignRight
129 verticalAlignment: Image.AlignTop
130 }
132 Dash {
133 id: dash
134 objectName: "dash"
136 available: !LightDM.Greeter.active
137 hides: [stages, launcher, panel.indicators]
138 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
139 !(panel.indicators.fullyOpened && !sideStageEnabled)
140 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
142 anchors {
143 fill: parent
144 topMargin: panel.panelHeight
145 }
147 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
148 opacity: 1.0 - disappearingAnimationProgress
149 property real disappearingAnimationProgress: stages.showProgress
151 // FIXME: only necessary because stages.showProgress is not animated
152 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
153 }
154 }
156 EdgeDragArea {
157 id: stagesDragArea
158 direction: Direction.Leftwards
160 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
161 width: shell.edgeSize
163 property real progress: stages.width
165 onTouchXChanged: {
166 if (status == DirectionalDragArea.Recognized) {
167 if (ApplicationManager.empty) {
168 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
169 } else {
170 progress = stages.width - stagesDragArea.width + touchX;
171 }
172 }
173 }
175 onDraggingChanged: {
176 if (!dragging) {
177 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
178 stages.show();
179 }
180 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
181 }
182 }
183 }
185 Item {
186 id: stages
187 objectName: "stages"
188 width: parent.width
189 height: parent.height
191 visible: !fullyHidden && !ApplicationManager.empty
193 x: {
194 if (shown) {
195 if (locked || greeter.fakeActiveForApp !== "") {
196 return 0;
197 }
198 return launcher.progress;
199 } else {
200 return stagesDragArea.progress
201 }
202 }
203 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
205 property bool shown: false
206 onShownChanged: {
207 if (shown) {
208 if (ApplicationManager.count > 0) {
209 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
210 }
211 } else {
212 if (ApplicationManager.focusedApplicationId) {
213 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
214 ApplicationManager.unfocusCurrentApplication();
215 }
216 }
217 }
219 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
220 // zero. That breaks the property binding and the function won't be reevaluated once
221 // shell.width is set, with the NaN result staying there for good.
222 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
224 property bool fullyShown: x == 0
225 property bool fullyHidden: x == width
227 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
229 // It might technically not be fullyShown but visually it just looks so.
230 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
232 function show() {
233 shown = true;
234 }
236 function hide() {
237 shown = false;
238 }
240 Connections {
241 target: ApplicationManager
242 onFocusRequested: {
243 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
244 lockscreen.show();
245 }
246 greeter.hide();
247 stages.show();
248 }
250 onFocusedApplicationIdChanged: {
251 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
252 lockscreen.show();
253 }
254 panel.indicators.hide();
255 }
257 onApplicationAdded: {
258 if (greeter.shown) {
259 greeter.hide();
260 }
261 if (!stages.shown) {
262 stages.show();
263 }
264 }
266 onEmptyChanged: {
267 if (ApplicationManager.empty) {
268 stages.hide();
269 }
270 }
271 }
273 Loader {
274 id: applicationsDisplayLoader
275 anchors.fill: parent
277 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
279 Binding {
280 target: applicationsDisplayLoader.item
281 property: "objectName"
282 value: "stage"
283 }
284 Binding {
285 target: applicationsDisplayLoader.item
286 property: "dragAreaWidth"
287 value: shell.edgeSize
288 }
289 Binding {
290 target: applicationsDisplayLoader.item
291 property: "maximizedAppTopMargin"
292 // Not just using panel.panelHeight as that changes depending on the focused app.
293 value: panel.indicators.panelHeight
294 }
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "interactive"
298 value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
299 && panel.indicators.fullyClosed
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "spreadEnabled"
304 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
305 }
306 }
307 }
309 InputMethod {
310 id: inputMethod
311 objectName: "inputMethod"
312 anchors { fill: parent; topMargin: panel.panelHeight }
313 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
314 }
316 Connections {
317 target: SurfaceManager
318 onSurfaceCreated: {
319 if (surface.type == MirSurfaceItem.InputMethod) {
320 inputMethod.surface = surface;
321 }
322 }
324 onSurfaceDestroyed: {
325 if (inputMethod.surface == surface) {
326 inputMethod.surface = null;
327 surface.parent = null;
328 }
329 if (!surface.parent) {
330 // there's no one displaying it. delete it right away
331 surface.release();
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 visible: required
349 width: parent.width
350 height: parent.height - panel.panelHeight
351 background: shell.background
352 minPinLength: 4
353 maxPinLength: 4
355 onEntered: LightDM.Greeter.respond(passphrase);
356 onCancel: greeter.show()
357 onEmergencyCall: {
358 greeter.fakeActiveForApp = "dialer-app"
359 shell.activateApplication("dialer-app")
360 lockscreen.hide()
361 }
363 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
365 Component.onCompleted: {
366 if (LightDM.Users.count == 1) {
367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
368 }
369 }
370 }
372 Connections {
373 target: LightDM.Greeter
375 onShowGreeter: greeter.show()
377 onShowPrompt: {
378 if (LightDM.Users.count == 1) {
379 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
380 if (text == "PIN") {
381 lockscreen.alphaNumeric = false
382 } else {
383 lockscreen.alphaNumeric = true
384 }
385 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
386 lockscreen.show();
387 }
388 }
390 onAuthenticationComplete: {
391 if (LightDM.Greeter.promptless) {
392 return;
393 }
394 if (LightDM.Greeter.authenticated) {
395 lockscreen.hide();
396 greeter.login();
397 } else {
398 lockscreen.clear(true);
399 }
400 }
401 }
403 Binding {
404 target: LightDM.Greeter
405 property: "active"
406 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
407 }
409 Rectangle {
410 anchors.fill: parent
411 color: "black"
412 opacity: greeterWrapper.showProgress * 0.8
413 }
415 Item {
416 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
417 id: greeterWrapper
418 x: launcher.progress
419 y: panel.panelHeight
420 width: parent.width
421 height: parent.height - panel.panelHeight
423 Behavior on x {
424 enabled: !launcher.dashSwipe
425 StandardAnimation {}
426 }
428 property bool fullyShown: showProgress === 1.0
429 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
430 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
432 Greeter {
433 id: greeter
434 objectName: "greeter"
436 signal sessionStarted() // helpful for tests
438 property string fakeActiveForApp: ""
440 available: true
441 hides: [launcher, panel.indicators]
442 shown: true
444 defaultBackground: shell.background
446 width: parent.width
447 height: parent.height
449 dragHandleWidth: shell.edgeSize
451 function login() {
452 enabled = false;
453 LightDM.Greeter.startSessionSync();
454 sessionStarted();
455 greeter.hide();
456 lockscreen.hide();
457 launcher.hide();
458 enabled = true;
459 }
461 onShownChanged: {
462 if (shown) {
463 lockscreen.reset();
464 if (!LightDM.Greeter.promptless) {
465 lockscreen.show();
466 }
467 // If there is only one user, we start authenticating with that one here.
468 // If there are more users, the Greeter will handle that
469 if (LightDM.Users.count == 1) {
470 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
471 }
472 greeter.fakeActiveForApp = "";
473 greeter.forceActiveFocus();
474 }
475 }
477 onUnlocked: greeter.hide()
478 onSelected: {
479 // Update launcher items for new user
480 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
481 AccountsService.user = user;
482 LauncherModel.setUser(user);
483 }
485 onTease: launcher.tease()
487 Binding {
488 target: ApplicationManager
489 property: "suspended"
490 value: greeter.shown && greeterWrapper.showProgress == 1
491 }
492 }
493 }
495 function showGreeterIfScreenIsOff() {
496 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
497 greeter.showNow()
498 }
499 }
501 Connections {
502 id: callConnection
503 target: callManager
504 onHasCallsChanged: showGreeterIfScreenIsOff()
505 }
507 Connections {
508 id: powerConnection
509 target: Powerd
511 onStatusChanged: {
512 showGreeterIfScreenIsOff()
514 // No reason to chew demo CPU when user isn't watching
515 if (Powerd.status === Powerd.Off) {
516 edgeDemo.paused = true;
517 } else if (Powerd.status === Powerd.On) {
518 edgeDemo.paused = false;
519 }
520 }
521 }
523 function showHome() {
524 if (edgeDemo.running) {
525 return
526 }
528 if (LightDM.Greeter.active) {
529 if (!LightDM.Greeter.promptless) {
530 lockscreen.show()
531 }
532 greeter.hide()
533 }
535 var animate = !LightDM.Greeter.active && !stages.shown
536 dash.setCurrentScope("clickscope", animate, false)
537 stages.hide()
538 }
540 function showDash() {
541 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
542 return;
543 }
545 if (!stages.locked) {
546 stages.hide();
547 launcher.fadeOut();
548 } else {
549 launcher.switchToNextState("visible");
550 }
552 if (greeter.shown) {
553 greeter.hideRight();
554 launcher.fadeOut();
555 }
556 }
558 Item {
559 id: overlay
560 z: 10
562 anchors.fill: parent
564 Panel {
565 id: panel
566 objectName: "panel"
567 anchors.fill: parent //because this draws indicator menus
568 indicators {
569 hides: [launcher]
570 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
571 contentEnabled: edgeDemo.panelContentEnabled
572 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
573 panelHeight: units.gu(3)
574 }
576 property bool topmostApplicationIsFullscreen:
577 ApplicationManager.focusedApplicationId &&
578 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
580 fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
581 && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
582 }
584 Launcher {
585 id: launcher
586 objectName: "launcher"
588 readonly property bool dashSwipe: progress > 0
590 anchors.top: parent.top
591 anchors.bottom: parent.bottom
592 width: parent.width
593 dragAreaWidth: shell.edgeSize
594 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
596 onShowDashHome: showHome()
597 onDash: showDash()
598 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
599 onLauncherApplicationSelected: {
600 if (greeter.fakeActiveForApp !== "") {
601 lockscreen.show()
602 }
603 if (!edgeDemo.running)
604 shell.activateApplication(appId)
605 }
606 onShownChanged: {
607 if (shown) {
608 panel.indicators.hide()
609 }
610 }
611 }
613 Rectangle {
614 id: modalNotificationBackground
616 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
617 color: "#000000"
618 anchors.fill: parent
619 opacity: 0.5
621 MouseArea {
622 anchors.fill: parent
623 }
624 }
626 Notifications {
627 id: notifications
629 model: NotificationBackend.Model
630 margin: units.gu(1)
632 y: panel.panelHeight
633 width: parent.width
634 height: parent.height - panel.panelHeight
636 states: [
637 State {
638 name: "narrow"
639 when: overlay.width <= units.gu(60)
640 AnchorChanges { target: notifications; anchors.left: parent.left }
641 },
642 State {
643 name: "wide"
644 when: overlay.width > units.gu(60)
645 AnchorChanges { target: notifications; anchors.left: undefined }
646 PropertyChanges { target: notifications; width: units.gu(38) }
647 }
648 ]
649 }
650 }
652 Binding {
653 target: i18n
654 property: "domain"
655 value: "unity8"
656 }
658 Dialogs {
659 id: dialogs
660 anchors.fill: parent
661 z: overlay.z + 10
662 onPowerOffClicked: {
663 shutdownFadeOutRectangle.enabled = true;
664 shutdownFadeOutRectangle.visible = true;
665 shutdownFadeOut.start();
666 }
667 }
669 Label {
670 id: alphaDisclaimerLabel
671 anchors.centerIn: parent
672 visible: ApplicationManager.fake ? ApplicationManager.fake : false
673 z: dialogs.z + 10
674 text: "EARLY ALPHA\nNOT READY FOR USE"
675 color: "lightgrey"
676 opacity: 0.2
677 font.weight: Font.Black
678 horizontalAlignment: Text.AlignHCenter
679 verticalAlignment: Text.AlignVCenter
680 fontSizeMode: Text.Fit
681 rotation: -45
682 scale: Math.min(parent.width, parent.height) / width
683 }
685 EdgeDemo {
686 id: edgeDemo
687 z: alphaDisclaimerLabel.z + 10
688 greeter: greeter
689 launcher: launcher
690 dash: dash
691 indicators: panel.indicators
692 underlay: underlay
693 }
695 Connections {
696 target: SessionBroadcast
697 onShowHome: showHome()
698 }
700 Rectangle {
701 id: shutdownFadeOutRectangle
702 z: edgeDemo.z + 10
703 enabled: false
704 visible: false
705 color: "black"
706 anchors.fill: parent
707 opacity: 0.0
708 NumberAnimation on opacity {
709 id: shutdownFadeOut
710 from: 0.0
711 to: 1.0
712 onStopped: {
713 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
714 DBusUnitySessionService.Shutdown();
715 }
716 }
717 }
718 }