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 && !forcedUnlock
57 readonly property bool forcedUnlock: edgeDemo.running
58 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
60 property bool sideStageEnabled: shell.width >= units.gu(100)
61 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
63 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
64 property int failedLoginsDelayAttempts: 7 // number of failed logins
65 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
67 property int orientation
68 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
69 onDeviceOrientationAngleChanged: {
70 if (!OrientationLock.enabled) {
71 orientation = Screen.orientation;
74 readonly property bool orientationLockEnabled: OrientationLock.enabled
75 onOrientationLockEnabledChanged: {
76 if (orientationLockEnabled) {
77 OrientationLock.savedOrientation = Screen.orientation;
79 orientation = Screen.orientation;
83 function activateApplication(appId) {
84 if (ApplicationManager.findApplication(appId)) {
85 ApplicationManager.requestFocusApplication(appId);
87 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
88 ApplicationManager.startApplication(appId, execFlags);
92 function startLockedApp(app) {
94 console.warn("Called startLockedApp(%1) when not locked, ignoring".arg(app))
97 greeter.lockedApp = app
98 shell.activateApplication(app)
102 target: LauncherModel
103 property: "applicationManager"
104 value: ApplicationManager
107 Component.onCompleted: {
108 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
109 if (ApplicationManager.count > 0) {
110 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
112 if (orientationLockEnabled) {
113 orientation = OrientationLock.savedOrientation;
118 id: backgroundSettings
119 schema.id: "org.gnome.desktop.background"
121 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
122 onGSettingsPictureChanged: {
123 shell.background = gSettingsPicture
132 objectName: "dashCommunicator"
136 target: ApplicationManager
137 property: "forceDashActive"
138 value: launcher.shown || launcher.dashSwipe
143 // Handle but do not filter out volume keys
144 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
145 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
148 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
149 dialogs.onPowerKeyPressed();
150 event.accepted = true;
152 event.accepted = false;
157 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
158 dialogs.onPowerKeyReleased();
159 event.accepted = true;
161 event.accepted = false;
170 height: parent.height
171 visible: !ApplicationManager.empty
174 target: ApplicationManager
176 if (greeter.narrowMode) {
177 if (appId === "dialer-app" && callManager.hasCalls) {
178 // If we are in the middle of a call, make dialer lockedApp and show it.
179 // This can happen if user backs out of dialer back to greeter, then
180 // launches dialer again.
181 greeter.lockedApp = appId;
183 if (greeter.hasLockedApp) {
184 if (appId === greeter.lockedApp) {
185 lockscreen.hide() // show locked app
187 greeter.startUnlock() // show lockscreen if necessary
192 if (LightDM.Greeter.active) {
193 greeter.startUnlock()
198 onFocusedApplicationIdChanged: {
199 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
200 greeter.startUnlock()
202 panel.indicators.hide();
205 onApplicationAdded: {
206 if (greeter.shown && appId != "unity8-dash") {
207 greeter.startUnlock()
209 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
210 lockscreen.hide() // show locked app
217 id: applicationsDisplayLoader
218 objectName: "applicationsDisplayLoader"
221 // When we have a locked app, we only want to show that one app.
222 // FIXME: do this in a less traumatic way. We currently only allow
223 // locked apps in phone mode (see FIXME in Lockscreen component in
224 // this same file). When that changes, we need to do something
225 // nicer here. But this code is currently just to prevent a
226 // theoretical attack where user enters lockedApp mode, then makes
227 // the screen larger (maybe connects to monitor) and tries to enter
229 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
230 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
233 target: applicationsDisplayLoader.item
234 property: "objectName"
238 target: applicationsDisplayLoader.item
239 property: "dragAreaWidth"
240 value: shell.edgeSize
243 target: applicationsDisplayLoader.item
244 property: "maximizedAppTopMargin"
245 // Not just using panel.panelHeight as that changes depending on the focused app.
246 value: panel.indicators.panelHeight
249 target: applicationsDisplayLoader.item
250 property: "interactive"
251 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
254 target: applicationsDisplayLoader.item
255 property: "spreadEnabled"
256 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
259 target: applicationsDisplayLoader.item
260 property: "inverseProgress"
261 value: launcher.progress
264 target: applicationsDisplayLoader.item
265 property: "orientation"
266 value: shell.orientation
273 objectName: "inputMethod"
274 anchors { fill: parent; topMargin: panel.panelHeight }
275 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
279 target: SurfaceManager
281 if (surface.type == MirSurfaceItem.InputMethod) {
282 inputMethod.surface = surface;
286 onSurfaceDestroyed: {
287 if (inputMethod.surface == surface) {
288 inputMethod.surface = null;
289 surface.parent = null;
291 if (!surface.parent) {
292 // there's no one displaying it. delete it right away
298 target: SessionManager
300 if (!session.parentSession && !session.application) {
301 // nothing is using it. delete it right away
309 objectName: "lockscreen"
311 hides: [launcher, panel.indicators]
314 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
315 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
319 height: parent.height - panel.panelHeight
320 background: shell.background
321 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
325 // FIXME: We *should* show emergency dialer if there is a SIM present,
326 // regardless of whether the side stage is enabled. But right now,
327 // the assumption is that narrow screens are phones which have SIMs
328 // and wider screens are tablets which don't. When we do allow this
329 // on devices with a side stage and a SIM, work should be done to
330 // ensure that the main stage is disabled while the dialer is present
331 // in the side stage. See the FIXME in the stage loader in this file.
332 showEmergencyCallButton: !shell.sideStageEnabled
334 onEntered: LightDM.Greeter.respond(passphrase);
335 onCancel: greeter.show()
336 onEmergencyCall: startLockedApp("dialer-app")
338 onShownChanged: if (shown) greeter.lockedApp = ""
340 function maybeShow() {
341 if (!shell.forcedUnlock) {
350 if (lockscreen.delayMinutes > 0) {
351 lockscreen.delayMinutes -= 1
352 if (lockscreen.delayMinutes > 0) {
359 Component.onCompleted: {
360 if (greeter.narrowMode) {
361 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
367 target: LightDM.Greeter
369 onShowGreeter: greeter.show()
370 onHideGreeter: greeter.login()
373 if (greeter.narrowMode) {
374 if (isDefaultPrompt) {
375 if (lockscreen.alphaNumeric) {
376 lockscreen.infoText = i18n.tr("Enter passphrase")
377 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
378 i18n.tr("Please re-enter")
380 lockscreen.infoText = i18n.tr("Enter passcode")
381 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
384 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
385 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
388 lockscreen.maybeShow();
392 onPromptlessChanged: {
393 if (greeter.narrowMode) {
394 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
398 lockscreen.maybeShow();
403 onAuthenticationComplete: {
404 if (LightDM.Greeter.authenticated) {
405 AccountsService.failedLogins = 0
407 // Else only penalize user for a failed login if they actually were
408 // prompted for a password. We do this below after the promptless
411 if (LightDM.Greeter.promptless) {
415 if (LightDM.Greeter.authenticated) {
418 AccountsService.failedLogins++
419 if (maxFailedLogins >= 2) { // require at least a warning
420 if (AccountsService.failedLogins === maxFailedLogins - 1) {
421 var title = lockscreen.alphaNumeric ?
422 i18n.tr("Sorry, incorrect passphrase.") :
423 i18n.tr("Sorry, incorrect passcode.")
424 var text = i18n.tr("This will be your last attempt.") + " " +
425 (lockscreen.alphaNumeric ?
426 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
427 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
428 lockscreen.showInfoPopup(title, text)
429 } else if (AccountsService.failedLogins >= maxFailedLogins) {
430 SystemImage.factoryReset() // Ouch!
433 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
434 lockscreen.delayMinutes = failedLoginsDelayMinutes
435 forcedDelayTimer.start()
438 lockscreen.clear(true);
439 if (greeter.narrowMode) {
440 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447 target: LightDM.Greeter
449 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
455 opacity: greeterWrapper.showProgress * 0.8
459 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
461 objectName: "greeterWrapper"
462 x: greeter.narrowMode ? launcher.progress : 0
465 height: parent.height - panel.panelHeight
468 enabled: !launcher.dashSwipe
472 property bool fullyShown: showProgress === 1.0
473 onFullyShownChanged: {
474 // Wait until the greeter is completely covering lockscreen before resetting it.
475 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
477 lockscreen.maybeShow();
481 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
482 onShowProgressChanged: {
483 if (showProgress === 0) {
484 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
486 } else if (greeter.narrowMode) {
487 lockscreen.clear(false) // to reset focus if necessary
494 objectName: "greeter"
496 signal sessionStarted() // helpful for tests
498 property string lockedApp: ""
499 property bool hasLockedApp: lockedApp !== ""
502 hides: [launcher, panel.indicators]
504 loadContent: required || lockscreen.required // keeps content in memory for quick show()
508 defaultBackground: shell.background
511 height: parent.height
513 dragHandleWidth: shell.edgeSize
515 function startUnlock() {
517 if (!LightDM.Greeter.authenticated) {
518 lockscreen.maybeShow()
529 if (LightDM.Greeter.startSessionSync()) {
540 if (greeter.narrowMode) {
541 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
545 greeter.lockedApp = "";
546 greeter.forceActiveFocus();
550 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
551 Component.onCompleted: {
552 Connectivity.unlockAllModems()
555 onUnlocked: greeter.hide()
557 // Update launcher items for new user
558 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
559 AccountsService.user = user;
560 LauncherModel.setUser(user);
563 onTease: launcher.tease()
566 target: ApplicationManager
567 property: "suspended"
568 value: greeter.shown && greeterWrapper.showProgress == 1
578 if (shell.locked && callManager.hasCalls) {
579 // We just received an incoming call while locked. The
580 // indicator will have already launched dialer-app for us, but
581 // there is a race between "hasCalls" changing and the dialer
582 // starting up. So in case we lose that race, we'll start/
583 // focus the dialer ourselves here too. Even if the indicator
584 // didn't launch the dialer for some reason (or maybe a call
585 // started via some other means), if an active call is
586 // happening, we want to be in the dialer.
587 startLockedApp("dialer-app")
597 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
603 function showHome() {
604 if (edgeDemo.running) {
608 if (LightDM.Greeter.active) {
609 greeter.startUnlock()
612 var animate = !LightDM.Greeter.active && !stages.shown
613 dash.setCurrentScope("clickscope", animate, false)
614 ApplicationManager.requestFocusApplication("unity8-dash")
617 function showDash() {
618 if (greeter.hasLockedApp || // just in case user gets here
619 (!greeter.narrowMode && shell.locked)) {
628 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
629 ApplicationManager.requestFocusApplication("unity8-dash")
643 anchors.fill: parent //because this draws indicator menus
646 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
647 contentEnabled: edgeDemo.panelContentEnabled
648 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
649 panelHeight: units.gu(3)
652 property bool topmostApplicationIsFullscreen:
653 ApplicationManager.focusedApplicationId &&
654 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
656 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
657 || greeter.hasLockedApp
662 objectName: "launcher"
664 readonly property bool dashSwipe: progress > 0
666 anchors.top: parent.top
667 anchors.bottom: parent.bottom
669 dragAreaWidth: shell.edgeSize
670 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
672 onShowDashHome: showHome()
674 onDashSwipeChanged: {
676 dash.setCurrentScope("clickscope", false, true)
679 onLauncherApplicationSelected: {
680 if (greeter.hasLockedApp) {
681 greeter.startUnlock()
683 if (!edgeDemo.running)
684 shell.activateApplication(appId)
688 panel.indicators.hide()
694 id: modalNotificationBackground
696 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
709 model: NotificationBackend.Model
714 height: parent.height - panel.panelHeight
719 when: overlay.width <= units.gu(60)
720 AnchorChanges { target: notifications; anchors.left: parent.left }
724 when: overlay.width > units.gu(60)
725 AnchorChanges { target: notifications; anchors.left: undefined }
726 PropertyChanges { target: notifications; width: units.gu(38) }
737 shutdownFadeOutRectangle.enabled = true;
738 shutdownFadeOutRectangle.visible = true;
739 shutdownFadeOut.start();
744 id: alphaDisclaimerLabel
745 anchors.centerIn: parent
746 visible: ApplicationManager.fake ? ApplicationManager.fake : false
748 text: "EARLY ALPHA\nNOT READY FOR USE"
751 font.weight: Font.Black
752 horizontalAlignment: Text.AlignHCenter
753 verticalAlignment: Text.AlignVCenter
754 fontSizeMode: Text.Fit
756 scale: Math.min(parent.width, parent.height) / width
761 objectName: "edgeDemo"
762 z: alphaDisclaimerLabel.z + 10
763 paused: Powerd.status === Powerd.Off // Saves power
766 indicators: panel.indicators
771 target: SessionBroadcast
772 onShowHome: showHome()
776 id: shutdownFadeOutRectangle
783 NumberAnimation on opacity {
788 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
789 DBusUnitySessionService.Shutdown();