on 1ea033d help sign in
unity8/qml/Shell.qml
738 lines 25 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 QtQuick.Window 2.0
19import AccountsService 0.1
20import GSettings 1.0
21import Unity.Application 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
29import Utils 0.1
30import LightDM 0.1 as LightDM
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stages"
39import "Panel/Indicators"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
44Item {
45 id: shell
47 // this is only here to select the width / height of the window if not running fullscreen
48 property bool tablet: false
49 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
50 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
52 property real edgeSize: units.gu(2)
53 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
54 property url background
55 readonly property real panelHeight: panel.panelHeight
57 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
59 property bool sideStageEnabled: shell.width >= units.gu(100)
60 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
62 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
63 property int failedLoginsDelayAttempts: 7 // number of failed logins
64 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
66 property int orientation
67 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
68 onDeviceOrientationAngleChanged: {
69 if (!OrientationLock.enabled) {
70 orientation = Screen.orientation;
71 }
72 }
73 readonly property bool orientationLockEnabled: OrientationLock.enabled
74 onOrientationLockEnabledChanged: {
75 if (orientationLockEnabled) {
76 OrientationLock.savedOrientation = Screen.orientation;
77 } else {
78 orientation = Screen.orientation;
79 }
80 }
82 function activateApplication(appId) {
83 if (ApplicationManager.findApplication(appId)) {
84 ApplicationManager.requestFocusApplication(appId);
85 } else {
86 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
87 ApplicationManager.startApplication(appId, execFlags);
88 }
89 }
91 function setFakeActiveForApp(app) {
92 if (shell.locked) {
93 greeter.fakeActiveForApp = app
94 lockscreen.hide()
95 }
96 }
98 Binding {
99 target: LauncherModel
100 property: "applicationManager"
101 value: ApplicationManager
102 }
104 Component.onCompleted: {
105 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
106 if (ApplicationManager.count > 0) {
107 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
108 }
109 if (orientationLockEnabled) {
110 orientation = OrientationLock.savedOrientation;
111 }
112 }
114 GSettings {
115 id: backgroundSettings
116 schema.id: "org.gnome.desktop.background"
117 }
118 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
119 onGSettingsPictureChanged: {
120 shell.background = gSettingsPicture
121 }
123 VolumeControl {
124 id: volumeControl
125 }
127 DashCommunicator {
128 id: dash
129 objectName: "dashCommunicator"
130 }
132 Binding {
133 target: ApplicationManager
134 property: "forceDashActive"
135 value: launcher.shown || launcher.dashSwipe
136 }
139 WindowKeysFilter {
140 // Handle but do not filter out volume keys
141 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
142 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
144 Keys.onPressed: {
145 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
146 dialogs.onPowerKeyPressed();
147 event.accepted = true;
148 } else {
149 event.accepted = false;
150 }
151 }
153 Keys.onReleased: {
154 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
155 dialogs.onPowerKeyReleased();
156 event.accepted = true;
157 } else {
158 event.accepted = false;
159 }
160 }
161 }
163 Item {
164 id: stages
165 objectName: "stages"
166 width: parent.width
167 height: parent.height
168 visible: !ApplicationManager.empty
170 Connections {
171 target: ApplicationManager
172 onFocusRequested: {
173 if (appId === "dialer-app") {
174 // Always let the dialer-app through. Either user asked
175 // for an emergency call or accepted an incoming call.
176 setFakeActiveForApp(appId)
177 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
178 lockscreen.show();
179 }
180 greeter.hide();
181 }
183 onFocusedApplicationIdChanged: {
184 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
185 lockscreen.show();
186 }
187 panel.indicators.hide();
188 }
190 onApplicationAdded: {
191 if (greeter.shown && appId != "unity8-dash") {
192 greeter.hide();
193 }
194 if (appId === "dialer-app") {
195 // Always let the dialer-app through. Either user asked
196 // for an emergency call or accepted an incoming call.
197 setFakeActiveForApp(appId)
198 }
199 launcher.hide();
200 }
201 }
203 Loader {
204 id: applicationsDisplayLoader
205 anchors.fill: parent
207 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
209 Binding {
210 target: applicationsDisplayLoader.item
211 property: "objectName"
212 value: "stage"
213 }
214 Binding {
215 target: applicationsDisplayLoader.item
216 property: "dragAreaWidth"
217 value: shell.edgeSize
218 }
219 Binding {
220 target: applicationsDisplayLoader.item
221 property: "maximizedAppTopMargin"
222 // Not just using panel.panelHeight as that changes depending on the focused app.
223 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
224 }
225 Binding {
226 target: applicationsDisplayLoader.item
227 property: "interactive"
228 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
229 }
230 Binding {
231 target: applicationsDisplayLoader.item
232 property: "spreadEnabled"
233 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
234 }
235 Binding {
236 target: applicationsDisplayLoader.item
237 property: "inverseProgress"
238 value: launcher.progress
239 }
240 Binding {
241 target: applicationsDisplayLoader.item
242 property: "orientation"
243 value: shell.orientation
244 }
245 }
246 }
248 InputMethod {
249 id: inputMethod
250 objectName: "inputMethod"
251 anchors { fill: parent; topMargin: panel.panelHeight }
252 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
253 }
255 Connections {
256 target: SurfaceManager
257 onSurfaceCreated: {
258 if (surface.type == MirSurfaceItem.InputMethod) {
259 inputMethod.surface = surface;
260 }
261 }
263 onSurfaceDestroyed: {
264 if (inputMethod.surface == surface) {
265 inputMethod.surface = null;
266 surface.parent = null;
267 }
268 if (!surface.parent) {
269 // there's no one displaying it. delete it right away
270 surface.release();
271 }
272 }
273 }
274 Connections {
275 target: SessionManager
276 onSessionStopping: {
277 if (!session.parentSession && !session.application) {
278 // nothing is using it. delete it right away
279 session.release();
280 }
281 }
282 }
284 Lockscreen {
285 id: lockscreen
286 objectName: "lockscreen"
288 hides: [launcher, panel.indicators]
289 shown: false
290 enabled: true
291 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
292 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
293 y: panel.panelHeight
294 visible: required
295 width: parent.width
296 height: parent.height - panel.panelHeight
297 background: shell.background
298 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
299 minPinLength: 4
300 maxPinLength: 4
302 // FIXME: We *should* show emergency dialer if there is a SIM present,
303 // regardless of whether the side stage is enabled. But right now,
304 // the assumption is that narrow screens are phones which have SIMs
305 // and wider screens are tablets which don't. When we do allow this
306 // on devices with a side stage and a SIM, work should be done to
307 // ensure that the main stage is disabled while the dialer is present
308 // in the side stage.
309 showEmergencyCallButton: !shell.sideStageEnabled
311 onEntered: LightDM.Greeter.respond(passphrase);
312 onCancel: greeter.show()
313 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
315 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
317 Timer {
318 id: forcedDelayTimer
319 interval: 1000 * 60
320 onTriggered: {
321 if (lockscreen.delayMinutes > 0) {
322 lockscreen.delayMinutes -= 1
323 if (lockscreen.delayMinutes > 0) {
324 start() // go again
325 }
326 }
327 }
328 }
330 Component.onCompleted: {
331 if (greeter.narrowMode) {
332 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
333 }
334 }
335 }
337 Connections {
338 target: LightDM.Greeter
340 onShowGreeter: greeter.show()
341 onHideGreeter: greeter.login()
343 onShowPrompt: {
344 if (greeter.narrowMode) {
345 if (isDefaultPrompt) {
346 if (lockscreen.alphaNumeric) {
347 lockscreen.infoText = i18n.tr("Enter passphrase")
348 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
349 i18n.tr("Please re-enter")
350 } else {
351 lockscreen.infoText = i18n.tr("Enter passcode")
352 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
353 }
354 } else {
355 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
356 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
357 }
359 lockscreen.show();
360 }
361 }
363 onPromptlessChanged: {
364 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
365 lockscreen.hide()
366 } else {
367 lockscreen.reset();
368 lockscreen.show();
369 }
370 }
372 onAuthenticationComplete: {
373 if (LightDM.Greeter.authenticated) {
374 AccountsService.failedLogins = 0
375 }
376 // Else only penalize user for a failed login if they actually were
377 // prompted for a password. We do this below after the promptless
378 // early exit.
380 if (LightDM.Greeter.promptless) {
381 return;
382 }
384 if (LightDM.Greeter.authenticated) {
385 greeter.login();
386 } else {
387 AccountsService.failedLogins++
388 if (maxFailedLogins >= 2) { // require at least a warning
389 if (AccountsService.failedLogins === maxFailedLogins - 1) {
390 var title = lockscreen.alphaNumeric ?
391 i18n.tr("Sorry, incorrect passphrase.") :
392 i18n.tr("Sorry, incorrect passcode.")
393 var text = i18n.tr("This will be your last attempt.") + " " +
394 (lockscreen.alphaNumeric ?
395 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
396 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
397 lockscreen.showInfoPopup(title, text)
398 } else if (AccountsService.failedLogins >= maxFailedLogins) {
399 SystemImage.factoryReset() // Ouch!
400 }
401 }
402 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
403 lockscreen.delayMinutes = failedLoginsDelayMinutes
404 forcedDelayTimer.start()
405 }
407 lockscreen.clear(true);
408 if (greeter.narrowMode) {
409 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
410 }
411 }
412 }
413 }
415 Binding {
416 target: LightDM.Greeter
417 property: "active"
418 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
419 }
421 Rectangle {
422 anchors.fill: parent
423 color: "black"
424 opacity: greeterWrapper.showProgress * 0.8
425 }
427 Item {
428 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
429 id: greeterWrapper
430 x: launcher.progress
431 y: panel.panelHeight
432 width: parent.width
433 height: parent.height - panel.panelHeight
435 Behavior on x {
436 enabled: !launcher.dashSwipe
437 StandardAnimation {}
438 }
440 property bool fullyShown: showProgress === 1.0
441 onFullyShownChanged: {
442 // Wait until the greeter is completely covering lockscreen before resetting it.
443 if (fullyShown && !LightDM.Greeter.authenticated) {
444 lockscreen.reset();
445 lockscreen.show();
446 }
447 }
449 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
450 onShowProgressChanged: {
451 if (showProgress === 0) {
452 if (LightDM.Greeter.authenticated) {
453 greeter.login()
454 } else if (greeter.narrowMode) {
455 lockscreen.clear(false) // to reset focus if necessary
456 }
457 }
458 }
460 Greeter {
461 id: greeter
462 objectName: "greeter"
464 signal sessionStarted() // helpful for tests
466 property string fakeActiveForApp: ""
468 available: true
469 hides: [launcher, panel.indicators]
470 shown: true
471 loadContent: required || lockscreen.required // keeps content in memory for quick show()
473 defaultBackground: shell.background
475 width: parent.width
476 height: parent.height
478 dragHandleWidth: shell.edgeSize
480 function login() {
481 enabled = false;
482 if (LightDM.Greeter.startSessionSync()) {
483 sessionStarted();
484 greeter.hide();
485 lockscreen.hide();
486 launcher.hide();
487 }
488 enabled = true;
489 }
491 onShownChanged: {
492 if (shown) {
493 if (greeter.narrowMode) {
494 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
495 }
496 greeter.fakeActiveForApp = "";
497 greeter.forceActiveFocus();
498 }
499 }
501 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
502 Component.onCompleted: {
503 Connectivity.unlockAllModems()
504 } */
506 onUnlocked: greeter.hide()
507 onSelected: {
508 // Update launcher items for new user
509 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
510 AccountsService.user = user;
511 LauncherModel.setUser(user);
512 }
514 onTease: launcher.tease()
516 Binding {
517 target: ApplicationManager
518 property: "suspended"
519 value: greeter.shown && greeterWrapper.showProgress == 1
520 }
521 }
522 }
524 Connections {
525 id: powerConnection
526 target: Powerd
528 onStatusChanged: {
529 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
530 greeter.showNow()
531 }
532 }
533 }
535 function showHome() {
536 if (edgeDemo.running) {
537 return
538 }
540 if (LightDM.Greeter.active) {
541 if (!LightDM.Greeter.authenticated) {
542 lockscreen.show()
543 }
544 greeter.hide()
545 }
547 var animate = !LightDM.Greeter.active && !stages.shown
548 dash.setCurrentScope("clickscope", animate, false)
549 ApplicationManager.requestFocusApplication("unity8-dash")
550 }
552 function showDash() {
553 if (greeter.fakeActiveForApp !== "") { // just in case user gets here
554 return
555 }
557 if (greeter.shown) {
558 greeter.hideRight();
559 launcher.fadeOut();
560 }
562 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
563 ApplicationManager.requestFocusApplication("unity8-dash")
564 launcher.fadeOut();
565 }
566 }
568 Item {
569 id: overlay
570 z: 10
572 anchors.fill: parent
574 Panel {
575 id: panel
576 objectName: "panel"
577 anchors.fill: parent //because this draws indicator menus
578 indicators {
579 hides: [launcher]
580 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
581 contentEnabled: edgeDemo.panelContentEnabled
582 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
584 minimizedPanelHeight: units.gu(3)
585 expandedPanelHeight: units.gu(7)
587 indicatorsModel: visibleIndicators.model
588 }
590 VisibleIndicators {
591 id: visibleIndicators
592 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
593 Component.onCompleted: initialise(indicatorProfile)
594 }
596 property bool topmostApplicationIsFullscreen:
597 ApplicationManager.focusedApplicationId &&
598 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
600 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
601 || greeter.fakeActiveForApp !== ""
602 }
604 Launcher {
605 id: launcher
606 objectName: "launcher"
608 readonly property bool dashSwipe: progress > 0
610 anchors.top: parent.top
611 anchors.bottom: parent.bottom
612 width: parent.width
613 dragAreaWidth: shell.edgeSize
614 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
616 onShowDashHome: showHome()
617 onDash: showDash()
618 onDashSwipeChanged: {
619 if (dashSwipe) {
620 dash.setCurrentScope("clickscope", false, true)
621 }
622 }
623 onLauncherApplicationSelected: {
624 if (greeter.fakeActiveForApp !== "") {
625 lockscreen.show()
626 }
627 if (!edgeDemo.running)
628 shell.activateApplication(appId)
629 }
630 onShownChanged: {
631 if (shown) {
632 panel.indicators.hide()
633 }
634 }
635 }
637 Rectangle {
638 id: modalNotificationBackground
640 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
641 color: "#000000"
642 anchors.fill: parent
643 opacity: 0.9
645 MouseArea {
646 anchors.fill: parent
647 }
648 }
650 Notifications {
651 id: notifications
653 model: NotificationBackend.Model
654 margin: units.gu(1)
656 y: panel.panelHeight
657 width: parent.width
658 height: parent.height - panel.panelHeight
660 states: [
661 State {
662 name: "narrow"
663 when: overlay.width <= units.gu(60)
664 AnchorChanges { target: notifications; anchors.left: parent.left }
665 },
666 State {
667 name: "wide"
668 when: overlay.width > units.gu(60)
669 AnchorChanges { target: notifications; anchors.left: undefined }
670 PropertyChanges { target: notifications; width: units.gu(38) }
671 }
672 ]
673 }
674 }
676 Dialogs {
677 id: dialogs
678 anchors.fill: parent
679 z: overlay.z + 10
680 onPowerOffClicked: {
681 shutdownFadeOutRectangle.enabled = true;
682 shutdownFadeOutRectangle.visible = true;
683 shutdownFadeOut.start();
684 }
685 }
687 Label {
688 id: alphaDisclaimerLabel
689 anchors.centerIn: parent
690 visible: ApplicationManager.fake ? ApplicationManager.fake : false
691 z: dialogs.z + 10
692 text: "EARLY ALPHA\nNOT READY FOR USE"
693 color: "lightgrey"
694 opacity: 0.2
695 font.weight: Font.Black
696 horizontalAlignment: Text.AlignHCenter
697 verticalAlignment: Text.AlignVCenter
698 fontSizeMode: Text.Fit
699 rotation: -45
700 scale: Math.min(parent.width, parent.height) / width
701 }
703 EdgeDemo {
704 id: edgeDemo
705 z: alphaDisclaimerLabel.z + 10
706 paused: Powerd.status === Powerd.Off // Saves power
707 greeter: greeter
708 launcher: launcher
709 indicators: panel.indicators
710 stages: stages
711 }
713 Connections {
714 target: SessionBroadcast
715 onShowHome: showHome()
716 }
718 Rectangle {
719 id: shutdownFadeOutRectangle
720 z: edgeDemo.z + 10
721 enabled: false
722 visible: false
723 color: "black"
724 anchors.fill: parent
725 opacity: 0.0
726 NumberAnimation on opacity {
727 id: shutdownFadeOut
728 from: 0.0
729 to: 1.0
730 onStopped: {
731 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
732 DBusUnitySessionService.Shutdown();
733 }
734 }
735 }
736 }