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 // Handle but do not filter out volume keys
133 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
134 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
137 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
138 dialogs.onPowerKeyPressed();
139 event.accepted = true;
141 event.accepted = false;
146 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
147 dialogs.onPowerKeyReleased();
148 event.accepted = true;
150 event.accepted = false;
159 height: parent.height
160 visible: !ApplicationManager.empty
163 target: ApplicationManager
165 if (appId === "dialer-app") {
166 // Always let the dialer-app through. Either user asked
167 // for an emergency call or accepted an incoming call.
168 setFakeActiveForApp(appId)
169 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
176 onFocusedApplicationIdChanged: {
177 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
180 panel.indicators.hide();
183 onApplicationAdded: {
184 if (greeter.shown && appId != "unity8-dash") {
187 if (appId === "dialer-app") {
188 // Always let the dialer-app through. Either user asked
189 // for an emergency call or accepted an incoming call.
190 setFakeActiveForApp(appId)
197 id: applicationsDisplayLoader
200 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
203 target: applicationsDisplayLoader.item
204 property: "objectName"
208 target: applicationsDisplayLoader.item
209 property: "dragAreaWidth"
210 value: shell.edgeSize
213 target: applicationsDisplayLoader.item
214 property: "maximizedAppTopMargin"
215 // Not just using panel.panelHeight as that changes depending on the focused app.
216 value: panel.indicators.panelHeight
219 target: applicationsDisplayLoader.item
220 property: "interactive"
221 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
224 target: applicationsDisplayLoader.item
225 property: "spreadEnabled"
226 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
229 target: applicationsDisplayLoader.item
230 property: "inverseProgress"
231 value: launcher.progress
234 target: applicationsDisplayLoader.item
235 property: "orientation"
236 value: shell.orientation
243 objectName: "inputMethod"
244 anchors { fill: parent; topMargin: panel.panelHeight }
245 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
249 target: SurfaceManager
251 if (surface.type == MirSurfaceItem.InputMethod) {
252 inputMethod.surface = surface;
256 onSurfaceDestroyed: {
257 if (inputMethod.surface == surface) {
258 inputMethod.surface = null;
259 surface.parent = null;
261 if (!surface.parent) {
262 // there's no one displaying it. delete it right away
268 target: SessionManager
270 if (!session.parentSession && !session.application) {
271 // nothing is using it. delete it right away
279 objectName: "lockscreen"
281 hides: [launcher, panel.indicators]
284 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
285 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
289 height: parent.height - panel.panelHeight
290 background: shell.background
291 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
295 // FIXME: We *should* show emergency dialer if there is a SIM present,
296 // regardless of whether the side stage is enabled. But right now,
297 // the assumption is that narrow screens are phones which have SIMs
298 // and wider screens are tablets which don't. When we do allow this
299 // on devices with a side stage and a SIM, work should be done to
300 // ensure that the main stage is disabled while the dialer is present
301 // in the side stage.
302 showEmergencyCallButton: !shell.sideStageEnabled
304 onEntered: LightDM.Greeter.respond(passphrase);
305 onCancel: greeter.show()
306 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
308 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
314 if (lockscreen.delayMinutes > 0) {
315 lockscreen.delayMinutes -= 1
316 if (lockscreen.delayMinutes > 0) {
323 Component.onCompleted: {
324 if (greeter.narrowMode) {
325 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
331 target: LightDM.Greeter
333 onShowGreeter: greeter.show()
334 onHideGreeter: greeter.login()
337 if (greeter.narrowMode) {
338 if (isDefaultPrompt) {
339 if (lockscreen.alphaNumeric) {
340 lockscreen.infoText = i18n.tr("Enter passphrase")
341 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
342 i18n.tr("Please re-enter")
344 lockscreen.infoText = i18n.tr("Enter passcode")
345 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
348 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
349 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
356 onPromptlessChanged: {
357 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
365 onAuthenticationComplete: {
366 if (LightDM.Greeter.authenticated) {
367 AccountsService.failedLogins = 0
369 // Else only penalize user for a failed login if they actually were
370 // prompted for a password. We do this below after the promptless
373 if (LightDM.Greeter.promptless) {
377 if (LightDM.Greeter.authenticated) {
380 AccountsService.failedLogins++
381 if (maxFailedLogins >= 2) { // require at least a warning
382 if (AccountsService.failedLogins === maxFailedLogins - 1) {
383 var title = lockscreen.alphaNumeric ?
384 i18n.tr("Sorry, incorrect passphrase.") :
385 i18n.tr("Sorry, incorrect passcode.")
386 var text = i18n.tr("This will be your last attempt.") + " " +
387 (lockscreen.alphaNumeric ?
388 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
389 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
390 lockscreen.showInfoPopup(title, text)
391 } else if (AccountsService.failedLogins >= maxFailedLogins) {
392 SystemImage.factoryReset() // Ouch!
395 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
396 lockscreen.delayMinutes = failedLoginsDelayMinutes
397 forcedDelayTimer.start()
400 lockscreen.clear(true);
401 if (greeter.narrowMode) {
402 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
409 target: LightDM.Greeter
411 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
417 opacity: greeterWrapper.showProgress * 0.8
421 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
426 height: parent.height - panel.panelHeight
429 enabled: !launcher.dashSwipe
433 property bool fullyShown: showProgress === 1.0
434 onFullyShownChanged: {
435 // Wait until the greeter is completely covering lockscreen before resetting it.
436 if (fullyShown && !LightDM.Greeter.authenticated) {
442 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
443 onShowProgressChanged: {
444 if (showProgress === 0) {
445 if (LightDM.Greeter.authenticated) {
447 } else if (greeter.narrowMode) {
448 lockscreen.clear(false) // to reset focus if necessary
455 objectName: "greeter"
457 signal sessionStarted() // helpful for tests
459 property string fakeActiveForApp: ""
462 hides: [launcher, panel.indicators]
464 loadContent: required || lockscreen.required // keeps content in memory for quick show()
466 defaultBackground: shell.background
469 height: parent.height
471 dragHandleWidth: shell.edgeSize
475 if (LightDM.Greeter.startSessionSync()) {
486 if (greeter.narrowMode) {
487 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
489 greeter.fakeActiveForApp = "";
490 greeter.forceActiveFocus();
494 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
495 Component.onCompleted: {
496 Connectivity.unlockAllModems()
499 onUnlocked: greeter.hide()
501 // Update launcher items for new user
502 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
503 AccountsService.user = user;
504 LauncherModel.setUser(user);
507 onTease: launcher.tease()
510 target: ApplicationManager
511 property: "suspended"
512 value: greeter.shown && greeterWrapper.showProgress == 1
522 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
528 function showHome() {
529 if (edgeDemo.running) {
533 if (LightDM.Greeter.active) {
534 if (!LightDM.Greeter.authenticated) {
540 var animate = !LightDM.Greeter.active && !stages.shown
541 dash.setCurrentScope("clickscope", animate, false)
542 ApplicationManager.requestFocusApplication("unity8-dash")
545 function showDash() {
546 if (greeter.fakeActiveForApp !== "") { // just in case user gets here
555 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
556 ApplicationManager.requestFocusApplication("unity8-dash")
570 anchors.fill: parent //because this draws indicator menus
573 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
574 contentEnabled: edgeDemo.panelContentEnabled
575 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
576 panelHeight: units.gu(3)
579 property bool topmostApplicationIsFullscreen:
580 ApplicationManager.focusedApplicationId &&
581 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
583 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
584 || greeter.fakeActiveForApp !== ""
589 objectName: "launcher"
591 readonly property bool dashSwipe: progress > 0
593 anchors.top: parent.top
594 anchors.bottom: parent.bottom
596 dragAreaWidth: shell.edgeSize
597 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
599 onShowDashHome: showHome()
601 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
605 onDashSwipeChanged: {
607 dash.setCurrentScope("clickscope", false, true)
610 onLauncherApplicationSelected: {
611 if (greeter.fakeActiveForApp !== "") {
614 if (!edgeDemo.running)
615 shell.activateApplication(appId)
619 panel.indicators.hide()
625 id: modalNotificationBackground
627 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
640 model: NotificationBackend.Model
645 height: parent.height - panel.panelHeight
650 when: overlay.width <= units.gu(60)
651 AnchorChanges { target: notifications; anchors.left: parent.left }
655 when: overlay.width > units.gu(60)
656 AnchorChanges { target: notifications; anchors.left: undefined }
657 PropertyChanges { target: notifications; width: units.gu(38) }
668 shutdownFadeOutRectangle.enabled = true;
669 shutdownFadeOutRectangle.visible = true;
670 shutdownFadeOut.start();
675 id: alphaDisclaimerLabel
676 anchors.centerIn: parent
677 visible: ApplicationManager.fake ? ApplicationManager.fake : false
679 text: "EARLY ALPHA\nNOT READY FOR USE"
682 font.weight: Font.Black
683 horizontalAlignment: Text.AlignHCenter
684 verticalAlignment: Text.AlignVCenter
685 fontSizeMode: Text.Fit
687 scale: Math.min(parent.width, parent.height) / width
692 z: alphaDisclaimerLabel.z + 10
693 paused: Powerd.status === Powerd.Off // Saves power
696 indicators: panel.indicators
701 target: SessionBroadcast
702 onShowHome: showHome()
706 id: shutdownFadeOutRectangle
713 NumberAnimation on opacity {
718 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
719 DBusUnitySessionService.Shutdown();