on 4e213d6 help sign in
unity8/qml/Shell.qml
771 lines 26 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 Unity.Notifications 1.0 as NotificationBackend
40import Unity.Session 0.1
41import Unity.DashCommunicator 0.1
43Item {
44 id: shell
46 // this is only here to select the width / height of the window if not running fullscreen
47 property bool tablet: false
48 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
49 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
51 property real edgeSize: units.gu(2)
52 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
53 property url background
54 readonly property real panelHeight: panel.panelHeight
56 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
58 property bool sideStageEnabled: shell.width >= units.gu(100)
59 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
61 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
62 property int failedLoginsDelayAttempts: 7 // number of failed logins
63 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
65 property int orientation
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67 onDeviceOrientationAngleChanged: {
68 if (!OrientationLock.enabled) {
69 orientation = Screen.orientation;
70 }
71 }
72 readonly property bool orientationLockEnabled: OrientationLock.enabled
73 onOrientationLockEnabledChanged: {
74 if (orientationLockEnabled) {
75 OrientationLock.savedOrientation = Screen.orientation;
76 } else {
77 orientation = Screen.orientation;
78 }
79 }
81 function activateApplication(appId) {
82 if (ApplicationManager.findApplication(appId)) {
83 ApplicationManager.requestFocusApplication(appId);
84 } else {
85 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
86 ApplicationManager.startApplication(appId, execFlags);
87 }
88 }
90 function startLockedApp(app) {
91 if (!shell.locked) {
92 console.warn("Called startLockedApp(%1) when not locked, ignoring".arg(app))
93 return
94 }
95 greeter.lockedApp = app
96 shell.activateApplication(app)
97 }
99 Binding {
100 target: LauncherModel
101 property: "applicationManager"
102 value: ApplicationManager
103 }
105 Component.onCompleted: {
106 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
107 if (ApplicationManager.count > 0) {
108 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
109 }
110 if (orientationLockEnabled) {
111 orientation = OrientationLock.savedOrientation;
112 }
113 }
115 GSettings {
116 id: backgroundSettings
117 schema.id: "org.gnome.desktop.background"
118 }
119 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
120 onGSettingsPictureChanged: {
121 shell.background = gSettingsPicture
122 }
124 VolumeControl {
125 id: volumeControl
126 }
128 DashCommunicator {
129 id: dash
130 objectName: "dashCommunicator"
131 }
133 Binding {
134 target: ApplicationManager
135 property: "forceDashActive"
136 value: launcher.shown || launcher.dashSwipe
137 }
140 WindowKeysFilter {
141 // Handle but do not filter out volume keys
142 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
143 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
145 Keys.onPressed: {
146 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
147 dialogs.onPowerKeyPressed();
148 event.accepted = true;
149 } else {
150 event.accepted = false;
151 }
152 }
154 Keys.onReleased: {
155 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
156 dialogs.onPowerKeyReleased();
157 event.accepted = true;
158 } else {
159 event.accepted = false;
160 }
161 }
162 }
164 Item {
165 id: stages
166 objectName: "stages"
167 width: parent.width
168 height: parent.height
169 visible: !ApplicationManager.empty
171 Connections {
172 target: ApplicationManager
173 onFocusRequested: {
174 if (greeter.narrowMode) {
175 if (greeter.hasLockedApp) {
176 if (appId === greeter.lockedApp) {
177 lockscreen.hide() // show locked app
178 } else {
179 greeter.startUnlock() // show lockscreen if necessary
180 }
181 }
182 greeter.hide();
183 } else {
184 if (LightDM.Greeter.active) {
185 greeter.startUnlock()
186 }
187 }
188 }
190 onFocusedApplicationIdChanged: {
191 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
192 greeter.startUnlock()
193 }
194 panel.indicators.hide();
195 }
197 onApplicationAdded: {
198 if (greeter.shown && appId != "unity8-dash") {
199 greeter.startUnlock()
200 }
201 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
202 lockscreen.hide() // show locked app
203 }
204 launcher.hide();
205 }
206 }
208 Loader {
209 id: applicationsDisplayLoader
210 objectName: "applicationsDisplayLoader"
211 anchors.fill: parent
213 // When we have a locked app, we only want to show that one app
214 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
215 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
217 Binding {
218 target: applicationsDisplayLoader.item
219 property: "objectName"
220 value: "stage"
221 }
222 Binding {
223 target: applicationsDisplayLoader.item
224 property: "dragAreaWidth"
225 value: shell.edgeSize
226 }
227 Binding {
228 target: applicationsDisplayLoader.item
229 property: "maximizedAppTopMargin"
230 // Not just using panel.panelHeight as that changes depending on the focused app.
231 value: panel.indicators.panelHeight
232 }
233 Binding {
234 target: applicationsDisplayLoader.item
235 property: "interactive"
236 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
237 }
238 Binding {
239 target: applicationsDisplayLoader.item
240 property: "spreadEnabled"
241 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
242 }
243 Binding {
244 target: applicationsDisplayLoader.item
245 property: "inverseProgress"
246 value: launcher.progress
247 }
248 Binding {
249 target: applicationsDisplayLoader.item
250 property: "orientation"
251 value: shell.orientation
252 }
253 }
254 }
256 InputMethod {
257 id: inputMethod
258 objectName: "inputMethod"
259 anchors { fill: parent; topMargin: panel.panelHeight }
260 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
261 }
263 Connections {
264 target: SurfaceManager
265 onSurfaceCreated: {
266 if (surface.type == MirSurfaceItem.InputMethod) {
267 inputMethod.surface = surface;
268 }
269 }
271 onSurfaceDestroyed: {
272 if (inputMethod.surface == surface) {
273 inputMethod.surface = null;
274 surface.parent = null;
275 }
276 if (!surface.parent) {
277 // there's no one displaying it. delete it right away
278 surface.release();
279 }
280 }
281 }
282 Connections {
283 target: SessionManager
284 onSessionStopping: {
285 if (!session.parentSession && !session.application) {
286 // nothing is using it. delete it right away
287 session.release();
288 }
289 }
290 }
292 Lockscreen {
293 id: lockscreen
294 objectName: "lockscreen"
296 hides: [launcher, panel.indicators]
297 shown: false
298 enabled: true
299 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
300 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
301 y: panel.panelHeight
302 visible: required
303 width: parent.width
304 height: parent.height - panel.panelHeight
305 background: shell.background
306 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
307 minPinLength: 4
308 maxPinLength: 4
310 // FIXME: We *should* show emergency dialer if there is a SIM present,
311 // regardless of whether the side stage is enabled. But right now,
312 // the assumption is that narrow screens are phones which have SIMs
313 // and wider screens are tablets which don't. When we do allow this
314 // on devices with a side stage and a SIM, work should be done to
315 // ensure that the main stage is disabled while the dialer is present
316 // in the side stage.
317 showEmergencyCallButton: !shell.sideStageEnabled
319 onEntered: LightDM.Greeter.respond(passphrase);
320 onCancel: greeter.show()
321 onEmergencyCall: startLockedApp("dialer-app")
323 onShownChanged: if (shown) greeter.lockedApp = ""
325 Timer {
326 id: forcedDelayTimer
327 interval: 1000 * 60
328 onTriggered: {
329 if (lockscreen.delayMinutes > 0) {
330 lockscreen.delayMinutes -= 1
331 if (lockscreen.delayMinutes > 0) {
332 start() // go again
333 }
334 }
335 }
336 }
338 Component.onCompleted: {
339 if (greeter.narrowMode) {
340 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
341 }
342 }
343 }
345 Connections {
346 target: LightDM.Greeter
348 onShowGreeter: greeter.show()
349 onHideGreeter: greeter.login()
351 onShowPrompt: {
352 if (greeter.narrowMode) {
353 if (isDefaultPrompt) {
354 if (lockscreen.alphaNumeric) {
355 lockscreen.infoText = i18n.tr("Enter passphrase")
356 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
357 i18n.tr("Please re-enter")
358 } else {
359 lockscreen.infoText = i18n.tr("Enter passcode")
360 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
361 }
362 } else {
363 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
364 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
365 }
367 lockscreen.show();
368 }
369 }
371 onPromptlessChanged: {
372 if (greeter.narrowMode) {
373 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
374 lockscreen.hide()
375 } else {
376 lockscreen.reset();
377 lockscreen.show();
378 }
379 }
380 }
382 onAuthenticationComplete: {
383 if (LightDM.Greeter.authenticated) {
384 AccountsService.failedLogins = 0
385 }
386 // Else only penalize user for a failed login if they actually were
387 // prompted for a password. We do this below after the promptless
388 // early exit.
390 if (LightDM.Greeter.promptless) {
391 return;
392 }
394 if (LightDM.Greeter.authenticated) {
395 greeter.login();
396 } else {
397 AccountsService.failedLogins++
398 if (maxFailedLogins >= 2) { // require at least a warning
399 if (AccountsService.failedLogins === maxFailedLogins - 1) {
400 var title = lockscreen.alphaNumeric ?
401 i18n.tr("Sorry, incorrect passphrase.") :
402 i18n.tr("Sorry, incorrect passcode.")
403 var text = i18n.tr("This will be your last attempt.") + " " +
404 (lockscreen.alphaNumeric ?
405 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
406 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
407 lockscreen.showInfoPopup(title, text)
408 } else if (AccountsService.failedLogins >= maxFailedLogins) {
409 SystemImage.factoryReset() // Ouch!
410 }
411 }
412 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
413 lockscreen.delayMinutes = failedLoginsDelayMinutes
414 forcedDelayTimer.start()
415 }
417 lockscreen.clear(true);
418 if (greeter.narrowMode) {
419 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
420 }
421 }
422 }
423 }
425 Binding {
426 target: LightDM.Greeter
427 property: "active"
428 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
429 }
431 Rectangle {
432 anchors.fill: parent
433 color: "black"
434 opacity: greeterWrapper.showProgress * 0.8
435 }
437 Item {
438 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
439 id: greeterWrapper
440 x: greeter.narrowMode ? launcher.progress : 0
441 y: panel.panelHeight
442 width: parent.width
443 height: parent.height - panel.panelHeight
445 Behavior on x {
446 enabled: !launcher.dashSwipe
447 StandardAnimation {}
448 }
450 property bool fullyShown: showProgress === 1.0
451 onFullyShownChanged: {
452 // Wait until the greeter is completely covering lockscreen before resetting it.
453 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
454 lockscreen.reset();
455 lockscreen.show();
456 }
457 }
459 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
460 onShowProgressChanged: {
461 if (showProgress === 0) {
462 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
463 greeter.login()
464 } else if (greeter.narrowMode) {
465 lockscreen.clear(false) // to reset focus if necessary
466 }
467 }
468 }
470 Greeter {
471 id: greeter
472 objectName: "greeter"
474 signal sessionStarted() // helpful for tests
476 property string lockedApp: ""
477 property bool hasLockedApp: lockedApp !== ""
479 available: true
480 hides: [launcher, panel.indicators]
481 shown: true
482 loadContent: required || lockscreen.required // keeps content in memory for quick show()
484 locked: shell.locked
486 defaultBackground: shell.background
488 width: parent.width
489 height: parent.height
491 dragHandleWidth: shell.edgeSize
493 function startUnlock() {
494 if (narrowMode) {
495 if (!LightDM.Greeter.authenticated) {
496 lockscreen.show()
497 }
498 hide()
499 } else {
500 show()
501 tryToUnlock()
502 }
503 }
505 function login() {
506 enabled = false;
507 if (LightDM.Greeter.startSessionSync()) {
508 sessionStarted();
509 greeter.hide();
510 lockscreen.hide();
511 launcher.hide();
512 }
513 enabled = true;
514 }
516 onShownChanged: {
517 if (shown) {
518 if (greeter.narrowMode) {
519 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
520 } else {
521 reset()
522 }
523 greeter.lockedApp = "";
524 greeter.forceActiveFocus();
525 }
526 }
528 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
529 Component.onCompleted: {
530 Connectivity.unlockAllModems()
531 } */
533 onUnlocked: greeter.hide()
534 onSelected: {
535 // Update launcher items for new user
536 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
537 AccountsService.user = user;
538 LauncherModel.setUser(user);
539 }
541 onTease: launcher.tease()
543 Binding {
544 target: ApplicationManager
545 property: "suspended"
546 value: greeter.shown && greeterWrapper.showProgress == 1
547 }
548 }
549 }
551 Connections {
552 id: callConnection
553 target: callManager
555 onHasCallsChanged: {
556 if (shell.locked && callManager.hasCalls) {
557 // We just received an incoming call while locked. The
558 // indicator will have already launched dialer-app for us, but
559 // there is a race between "hasCalls" changing and the dialer
560 // starting up. So in case we lose that race, we'll start/
561 // focus the dialer ourselves here too. Even if the indicator
562 // didn't launch the dialer for some reason (or maybe a call
563 // started via some other means), if an active call is
564 // happening, we want to be in the dialer.
565 startLockedApp("dialer-app")
566 }
567 }
568 }
570 Connections {
571 id: powerConnection
572 target: Powerd
574 onStatusChanged: {
575 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
576 greeter.showNow()
577 }
578 }
579 }
581 function showHome() {
582 if (edgeDemo.running) {
583 return
584 }
586 if (LightDM.Greeter.active) {
587 greeter.startUnlock()
588 }
590 var animate = !LightDM.Greeter.active && !stages.shown
591 dash.setCurrentScope("clickscope", animate, false)
592 ApplicationManager.requestFocusApplication("unity8-dash")
593 }
595 function showDash() {
596 if (greeter.hasLockedApp) { // just in case user gets here
597 return
598 }
600 if (greeter.shown) {
601 greeter.hideRight();
602 launcher.fadeOut();
603 }
605 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
606 ApplicationManager.requestFocusApplication("unity8-dash")
607 launcher.fadeOut();
608 }
609 }
611 Item {
612 id: overlay
613 z: 10
615 anchors.fill: parent
617 Panel {
618 id: panel
619 objectName: "panel"
620 anchors.fill: parent //because this draws indicator menus
621 indicators {
622 hides: [launcher]
623 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
624 contentEnabled: edgeDemo.panelContentEnabled
625 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
626 panelHeight: units.gu(3)
627 }
629 property bool topmostApplicationIsFullscreen:
630 ApplicationManager.focusedApplicationId &&
631 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
633 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
634 || greeter.hasLockedApp
635 }
637 Launcher {
638 id: launcher
639 objectName: "launcher"
641 readonly property bool dashSwipe: progress > 0
643 anchors.top: parent.top
644 anchors.bottom: parent.bottom
645 width: parent.width
646 dragAreaWidth: shell.edgeSize
647 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
649 onShowDashHome: showHome()
650 onDash: showDash()
651 onDashSwipeChanged: {
652 if (dashSwipe) {
653 dash.setCurrentScope("clickscope", false, true)
654 }
655 }
656 onLauncherApplicationSelected: {
657 if (greeter.hasLockedApp) {
658 greeter.startUnlock()
659 }
660 if (!edgeDemo.running)
661 shell.activateApplication(appId)
662 }
663 onShownChanged: {
664 if (shown) {
665 panel.indicators.hide()
666 }
667 }
668 }
670 Rectangle {
671 id: modalNotificationBackground
673 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
674 color: "#000000"
675 anchors.fill: parent
676 opacity: 0.9
678 MouseArea {
679 anchors.fill: parent
680 }
681 }
683 Notifications {
684 id: notifications
686 model: NotificationBackend.Model
687 margin: units.gu(1)
689 y: panel.panelHeight
690 width: parent.width
691 height: parent.height - panel.panelHeight
693 states: [
694 State {
695 name: "narrow"
696 when: overlay.width <= units.gu(60)
697 AnchorChanges { target: notifications; anchors.left: parent.left }
698 },
699 State {
700 name: "wide"
701 when: overlay.width > units.gu(60)
702 AnchorChanges { target: notifications; anchors.left: undefined }
703 PropertyChanges { target: notifications; width: units.gu(38) }
704 }
705 ]
706 }
707 }
709 Dialogs {
710 id: dialogs
711 anchors.fill: parent
712 z: overlay.z + 10
713 onPowerOffClicked: {
714 shutdownFadeOutRectangle.enabled = true;
715 shutdownFadeOutRectangle.visible = true;
716 shutdownFadeOut.start();
717 }
718 }
720 Label {
721 id: alphaDisclaimerLabel
722 anchors.centerIn: parent
723 visible: ApplicationManager.fake ? ApplicationManager.fake : false
724 z: dialogs.z + 10
725 text: "EARLY ALPHA\nNOT READY FOR USE"
726 color: "lightgrey"
727 opacity: 0.2
728 font.weight: Font.Black
729 horizontalAlignment: Text.AlignHCenter
730 verticalAlignment: Text.AlignVCenter
731 fontSizeMode: Text.Fit
732 rotation: -45
733 scale: Math.min(parent.width, parent.height) / width
734 }
736 EdgeDemo {
737 id: edgeDemo
738 z: alphaDisclaimerLabel.z + 10
739 paused: Powerd.status === Powerd.Off // Saves power
740 greeter: greeter
741 launcher: launcher
742 indicators: panel.indicators
743 stages: stages
744 }
746 Connections {
747 target: SessionBroadcast
748 onShowHome: showHome()
749 }
751 Rectangle {
752 id: shutdownFadeOutRectangle
753 z: edgeDemo.z + 10
754 enabled: false
755 visible: false
756 color: "black"
757 anchors.fill: parent
758 opacity: 0.0
759 NumberAnimation on opacity {
760 id: shutdownFadeOut
761 from: 0.0
762 to: 1.0
763 onStopped: {
764 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
765 DBusUnitySessionService.Shutdown();
766 }
767 }
768 }
769 }