2 * Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
18 import QtQuick.Window 2.0
19 import AccountsService 0.1
21 import Unity.Application 0.1
22 import Ubuntu.Components 0.1
23 import Ubuntu.Components.Popups 1.0
24 import Ubuntu.Gestures 0.1
25 import Ubuntu.SystemImage 0.1
26 import Ubuntu.Telephony 0.1 as Telephony
27 import Unity.Connectivity 0.1
28 import Unity.Launcher 0.1
30 import LightDM 0.1 as LightDM
32 import SessionBroadcast 0.1
37 import "Notifications"
39 import Unity.Notifications 1.0 as NotificationBackend
40 import Unity.Session 0.1
41 import Unity.DashCommunicator 0.1
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;
72 readonly property bool orientationLockEnabled: OrientationLock.enabled
73 onOrientationLockEnabledChanged: {
74 if (orientationLockEnabled) {
75 OrientationLock.savedOrientation = Screen.orientation;
77 orientation = Screen.orientation;
81 function activateApplication(appId) {
82 if (ApplicationManager.findApplication(appId)) {
83 ApplicationManager.requestFocusApplication(appId);
85 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
86 ApplicationManager.startApplication(appId, execFlags);
90 function setFakeActiveForApp(app) {
92 greeter.fakeActiveForApp = app
99 property: "applicationManager"
100 value: ApplicationManager
103 Component.onCompleted: {
104 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
105 if (ApplicationManager.count > 0) {
106 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
108 if (orientationLockEnabled) {
109 orientation = OrientationLock.savedOrientation;
114 id: backgroundSettings
115 schema.id: "org.gnome.desktop.background"
117 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
118 onGSettingsPictureChanged: {
119 shell.background = gSettingsPicture
128 objectName: "dashCommunicator"
132 target: ApplicationManager
133 property: "forceDashActive"
134 value: launcher.shown || launcher.dashSwipe
139 // Handle but do not filter out volume keys
140 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
141 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
144 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
145 dialogs.onPowerKeyPressed();
146 event.accepted = true;
148 event.accepted = false;
153 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
154 dialogs.onPowerKeyReleased();
155 event.accepted = true;
157 event.accepted = false;
166 height: parent.height
167 visible: !ApplicationManager.empty
170 target: ApplicationManager
172 if (appId === "dialer-app") {
173 // Always let the dialer-app through. Either user asked
174 // for an emergency call or accepted an incoming call.
175 setFakeActiveForApp(appId)
176 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
182 onFocusedApplicationIdChanged: {
183 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
186 panel.indicators.hide();
189 onApplicationAdded: {
190 if (greeter.shown && appId != "unity8-dash") {
193 if (appId === "dialer-app") {
194 // Always let the dialer-app through. Either user asked
195 // for an emergency call or accepted an incoming call.
196 setFakeActiveForApp(appId)
203 id: applicationsDisplayLoader
206 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
209 target: applicationsDisplayLoader.item
210 property: "objectName"
214 target: applicationsDisplayLoader.item
215 property: "dragAreaWidth"
216 value: shell.edgeSize
219 target: applicationsDisplayLoader.item
220 property: "maximizedAppTopMargin"
221 // Not just using panel.panelHeight as that changes depending on the focused app.
222 value: panel.indicators.panelHeight
225 target: applicationsDisplayLoader.item
226 property: "interactive"
227 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
230 target: applicationsDisplayLoader.item
231 property: "spreadEnabled"
232 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
235 target: applicationsDisplayLoader.item
236 property: "inverseProgress"
237 value: launcher.progress
240 target: applicationsDisplayLoader.item
241 property: "orientation"
242 value: shell.orientation
249 objectName: "inputMethod"
250 anchors { fill: parent; topMargin: panel.panelHeight }
251 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
255 target: SurfaceManager
257 if (surface.type == MirSurfaceItem.InputMethod) {
258 inputMethod.surface = surface;
262 onSurfaceDestroyed: {
263 if (inputMethod.surface == surface) {
264 inputMethod.surface = null;
265 surface.parent = null;
267 if (!surface.parent) {
268 // there's no one displaying it. delete it right away
274 target: SessionManager
276 if (!session.parentSession && !session.application) {
277 // nothing is using it. delete it right away
285 objectName: "lockscreen"
287 hides: [launcher, panel.indicators]
290 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
291 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
295 height: parent.height - panel.panelHeight
296 background: shell.background
297 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
301 // FIXME: We *should* show emergency dialer if there is a SIM present,
302 // regardless of whether the side stage is enabled. But right now,
303 // the assumption is that narrow screens are phones which have SIMs
304 // and wider screens are tablets which don't. When we do allow this
305 // on devices with a side stage and a SIM, work should be done to
306 // ensure that the main stage is disabled while the dialer is present
307 // in the side stage.
308 showEmergencyCallButton: !shell.sideStageEnabled
310 onEntered: LightDM.Greeter.respond(passphrase);
311 onCancel: greeter.show()
312 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
314 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
320 if (lockscreen.delayMinutes > 0) {
321 lockscreen.delayMinutes -= 1
322 if (lockscreen.delayMinutes > 0) {
329 Component.onCompleted: {
330 if (greeter.narrowMode) {
331 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
337 target: LightDM.Greeter
339 onShowGreeter: greeter.show()
340 onHideGreeter: greeter.login()
343 if (greeter.narrowMode) {
344 if (isDefaultPrompt) {
345 if (lockscreen.alphaNumeric) {
346 lockscreen.infoText = i18n.tr("Enter passphrase")
347 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
348 i18n.tr("Please re-enter")
350 lockscreen.infoText = i18n.tr("Enter passcode")
351 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
354 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
355 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
362 onPromptlessChanged: {
363 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
371 onAuthenticationComplete: {
372 if (LightDM.Greeter.authenticated) {
373 AccountsService.failedLogins = 0
375 // Else only penalize user for a failed login if they actually were
376 // prompted for a password. We do this below after the promptless
379 if (LightDM.Greeter.promptless) {
383 if (LightDM.Greeter.authenticated) {
386 AccountsService.failedLogins++
387 if (maxFailedLogins >= 2) { // require at least a warning
388 if (AccountsService.failedLogins === maxFailedLogins - 1) {
389 var title = lockscreen.alphaNumeric ?
390 i18n.tr("Sorry, incorrect passphrase.") :
391 i18n.tr("Sorry, incorrect passcode.")
392 var text = i18n.tr("This will be your last attempt.") + " " +
393 (lockscreen.alphaNumeric ?
394 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
395 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
396 lockscreen.showInfoPopup(title, text)
397 } else if (AccountsService.failedLogins >= maxFailedLogins) {
398 SystemImage.factoryReset() // Ouch!
401 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
402 lockscreen.delayMinutes = failedLoginsDelayMinutes
403 forcedDelayTimer.start()
406 lockscreen.clear(true);
407 if (greeter.narrowMode) {
408 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
415 target: LightDM.Greeter
417 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
423 opacity: greeterWrapper.showProgress * 0.8
427 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
432 height: parent.height - panel.panelHeight
435 enabled: !launcher.dashSwipe
439 property bool fullyShown: showProgress === 1.0
440 onFullyShownChanged: {
441 // Wait until the greeter is completely covering lockscreen before resetting it.
442 if (fullyShown && !LightDM.Greeter.authenticated) {
448 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
449 onShowProgressChanged: {
450 if (showProgress === 0) {
451 if (LightDM.Greeter.authenticated) {
453 } else if (greeter.narrowMode) {
454 lockscreen.clear(false) // to reset focus if necessary
461 objectName: "greeter"
463 signal sessionStarted() // helpful for tests
465 property string fakeActiveForApp: ""
468 hides: [launcher, panel.indicators]
470 loadContent: required || lockscreen.required // keeps content in memory for quick show()
472 defaultBackground: shell.background
475 height: parent.height
477 dragHandleWidth: shell.edgeSize
481 if (LightDM.Greeter.startSessionSync()) {
492 if (greeter.narrowMode) {
493 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
495 greeter.fakeActiveForApp = "";
496 greeter.forceActiveFocus();
500 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
501 Component.onCompleted: {
502 Connectivity.unlockAllModems()
505 onUnlocked: greeter.hide()
507 // Update launcher items for new user
508 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
509 AccountsService.user = user;
510 LauncherModel.setUser(user);
513 onTease: launcher.tease()
516 target: ApplicationManager
517 property: "suspended"
518 value: greeter.shown && greeterWrapper.showProgress == 1
528 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
534 function showHome() {
535 if (edgeDemo.running) {
539 if (LightDM.Greeter.active) {
540 if (!LightDM.Greeter.authenticated) {
546 var animate = !LightDM.Greeter.active && !stages.shown
547 dash.setCurrentScope("clickscope", animate, false)
548 ApplicationManager.requestFocusApplication("unity8-dash")
551 function showDash() {
552 if (greeter.fakeActiveForApp !== "") { // just in case user gets here
561 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
562 ApplicationManager.requestFocusApplication("unity8-dash")
576 anchors.fill: parent //because this draws indicator menus
579 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
580 contentEnabled: edgeDemo.panelContentEnabled
581 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
582 panelHeight: units.gu(3)
585 property bool topmostApplicationIsFullscreen:
586 ApplicationManager.focusedApplicationId &&
587 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
589 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
590 || greeter.fakeActiveForApp !== ""
595 objectName: "launcher"
597 readonly property bool dashSwipe: progress > 0
599 anchors.top: parent.top
600 anchors.bottom: parent.bottom
602 dragAreaWidth: shell.edgeSize
603 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
605 onShowDashHome: showHome()
607 onDashSwipeChanged: {
609 dash.setCurrentScope("clickscope", false, true)
612 onLauncherApplicationSelected: {
613 if (greeter.fakeActiveForApp !== "") {
616 if (!edgeDemo.running)
617 shell.activateApplication(appId)
621 panel.indicators.hide()
627 id: modalNotificationBackground
629 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
642 model: NotificationBackend.Model
647 height: parent.height - panel.panelHeight
652 when: overlay.width <= units.gu(60)
653 AnchorChanges { target: notifications; anchors.left: parent.left }
657 when: overlay.width > units.gu(60)
658 AnchorChanges { target: notifications; anchors.left: undefined }
659 PropertyChanges { target: notifications; width: units.gu(38) }
670 shutdownFadeOutRectangle.enabled = true;
671 shutdownFadeOutRectangle.visible = true;
672 shutdownFadeOut.start();
677 id: alphaDisclaimerLabel
678 anchors.centerIn: parent
679 visible: ApplicationManager.fake ? ApplicationManager.fake : false
681 text: "EARLY ALPHA\nNOT READY FOR USE"
684 font.weight: Font.Black
685 horizontalAlignment: Text.AlignHCenter
686 verticalAlignment: Text.AlignVCenter
687 fontSizeMode: Text.Fit
689 scale: Math.min(parent.width, parent.height) / width
694 z: alphaDisclaimerLabel.z + 10
695 paused: Powerd.status === Powerd.Off // Saves power
698 indicators: panel.indicators
703 target: SessionBroadcast
704 onShowHome: showHome()
708 id: shutdownFadeOutRectangle
715 NumberAnimation on opacity {
720 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
721 DBusUnitySessionService.Shutdown();