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 "Panel/Indicators"
40 import Unity.Notifications 1.0 as NotificationBackend
41 import Unity.Session 0.1
42 import Unity.DashCommunicator 0.1
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: asImageTester.status == Image.Ready ? asImageTester.source
55 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
56 readonly property real panelHeight: panel.panelHeight
58 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
59 readonly property alias hasLockedApp: greeter.hasLockedApp
60 readonly property bool forcedUnlock: edgeDemo.running
61 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
63 property bool sideStageEnabled: shell.width >= units.gu(100)
64 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
66 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
67 property int failedLoginsDelayAttempts: 7 // number of failed logins
68 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
70 property int orientation
71 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
72 onDeviceOrientationAngleChanged: {
73 if (!OrientationLock.enabled) {
74 orientation = Screen.orientation;
77 readonly property bool orientationLockEnabled: OrientationLock.enabled
78 onOrientationLockEnabledChanged: {
79 if (orientationLockEnabled) {
80 OrientationLock.savedOrientation = Screen.orientation;
82 orientation = Screen.orientation;
86 function activateApplication(appId) {
87 if (ApplicationManager.findApplication(appId)) {
88 ApplicationManager.requestFocusApplication(appId);
90 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
91 ApplicationManager.startApplication(appId, execFlags);
95 function startLockedApp(app) {
97 greeter.lockedApp = app;
99 shell.activateApplication(app);
102 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
105 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
113 id: backgroundSettings
114 schema.id: "org.gnome.desktop.background"
117 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
120 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
128 target: LauncherModel
129 property: "applicationManager"
130 value: ApplicationManager
133 Component.onCompleted: {
134 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
135 if (ApplicationManager.count > 0) {
136 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
138 if (orientationLockEnabled) {
139 orientation = OrientationLock.savedOrientation;
149 objectName: "dashCommunicator"
155 enabled: Powerd.status === Powerd.On
159 target: ApplicationManager
160 property: "forceDashActive"
161 value: launcher.shown || launcher.dashSwipe
166 onVolumeDownPressed: volumeControl.volumeDown()
167 onVolumeUpPressed: volumeControl.volumeUp()
168 onBothVolumeKeysPressed: screenGrabber.capture()
173 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
174 dialogs.onPowerKeyPressed();
175 event.accepted = true;
177 volumeKeyFilter.onKeyPressed(event.key);
178 event.accepted = false;
183 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
184 dialogs.onPowerKeyReleased();
185 event.accepted = true;
187 volumeKeyFilter.onKeyReleased(event.key);
188 event.accepted = false;
197 height: parent.height
198 visible: !ApplicationManager.empty
201 target: ApplicationManager
203 if (greeter.narrowMode) {
204 if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
205 // If we are in the middle of a call, make dialer lockedApp and show it.
206 // This can happen if user backs out of dialer back to greeter, then
207 // launches dialer again.
208 greeter.lockedApp = appId;
210 if (greeter.hasLockedApp) {
211 if (appId === greeter.lockedApp) {
212 lockscreen.hide() // show locked app
214 greeter.startUnlock() // show lockscreen if necessary
219 if (LightDM.Greeter.active) {
220 greeter.startUnlock()
225 onFocusedApplicationIdChanged: {
226 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
227 greeter.startUnlock()
229 panel.indicators.hide();
232 onApplicationAdded: {
233 if (greeter.shown && appId != "unity8-dash") {
234 greeter.startUnlock()
236 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
237 lockscreen.hide() // show locked app
244 id: applicationsDisplayLoader
245 objectName: "applicationsDisplayLoader"
248 // When we have a locked app, we only want to show that one app.
249 // FIXME: do this in a less traumatic way. We currently only allow
250 // locked apps in phone mode (see FIXME in Lockscreen component in
251 // this same file). When that changes, we need to do something
252 // nicer here. But this code is currently just to prevent a
253 // theoretical attack where user enters lockedApp mode, then makes
254 // the screen larger (maybe connects to monitor) and tries to enter
256 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
257 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
260 target: applicationsDisplayLoader.item
261 property: "objectName"
265 target: applicationsDisplayLoader.item
266 property: "dragAreaWidth"
267 value: shell.edgeSize
270 target: applicationsDisplayLoader.item
271 property: "maximizedAppTopMargin"
272 // Not just using panel.panelHeight as that changes depending on the focused app.
273 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
276 target: applicationsDisplayLoader.item
277 property: "interactive"
278 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
281 target: applicationsDisplayLoader.item
282 property: "spreadEnabled"
283 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
286 target: applicationsDisplayLoader.item
287 property: "inverseProgress"
288 value: launcher.progress
291 target: applicationsDisplayLoader.item
292 property: "orientation"
293 value: shell.orientation
300 objectName: "inputMethod"
301 anchors { fill: parent; topMargin: panel.panelHeight }
302 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
306 target: SurfaceManager
308 if (surface.type == MirSurfaceItem.InputMethod) {
309 inputMethod.surface = surface;
313 onSurfaceDestroyed: {
314 if (inputMethod.surface == surface) {
315 inputMethod.surface = null;
316 surface.parent = null;
318 if (!surface.parent) {
319 // there's no one displaying it. delete it right away
325 target: SessionManager
327 if (!session.parentSession && !session.application) {
328 // nothing is using it. delete it right away
336 objectName: "lockscreen"
338 hides: [launcher, panel.indicators]
341 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
342 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
346 height: parent.height - panel.panelHeight
347 background: shell.background
348 darkenBackground: 0.4
349 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
353 // FIXME: We *should* show emergency dialer if there is a SIM present,
354 // regardless of whether the side stage is enabled. But right now,
355 // the assumption is that narrow screens are phones which have SIMs
356 // and wider screens are tablets which don't. When we do allow this
357 // on devices with a side stage and a SIM, work should be done to
358 // ensure that the main stage is disabled while the dialer is present
359 // in the side stage. See the FIXME in the stage loader in this file.
360 showEmergencyCallButton: !shell.sideStageEnabled
362 onEntered: LightDM.Greeter.respond(passphrase);
363 onCancel: greeter.show()
364 onEmergencyCall: startLockedApp("dialer-app")
366 onShownChanged: if (shown) greeter.lockedApp = ""
368 function maybeShow() {
369 if (!shell.forcedUnlock) {
378 if (lockscreen.delayMinutes > 0) {
379 lockscreen.delayMinutes -= 1
380 if (lockscreen.delayMinutes > 0) {
387 Component.onCompleted: {
388 if (greeter.narrowMode) {
389 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
395 target: LightDM.Greeter
397 onShowGreeter: greeter.show()
398 onHideGreeter: greeter.login()
401 shell.enabled = true;
402 if (!LightDM.Greeter.active) {
403 return; // could happen if hideGreeter() comes in before we prompt
405 if (greeter.narrowMode) {
406 if (isDefaultPrompt) {
407 if (lockscreen.alphaNumeric) {
408 lockscreen.infoText = i18n.tr("Enter passphrase")
409 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
410 i18n.tr("Please re-enter")
412 lockscreen.infoText = i18n.tr("Enter passcode")
413 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
416 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
417 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
420 lockscreen.maybeShow();
424 onPromptlessChanged: {
425 if (!LightDM.Greeter.active) {
426 return; // could happen if hideGreeter() comes in before we prompt
428 if (greeter.narrowMode) {
429 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
433 lockscreen.maybeShow();
438 onAuthenticationComplete: {
439 shell.enabled = true;
440 if (LightDM.Greeter.authenticated) {
441 AccountsService.failedLogins = 0
443 // Else only penalize user for a failed login if they actually were
444 // prompted for a password. We do this below after the promptless
447 if (LightDM.Greeter.promptless) {
451 if (LightDM.Greeter.authenticated) {
454 AccountsService.failedLogins++
455 if (maxFailedLogins >= 2) { // require at least a warning
456 if (AccountsService.failedLogins === maxFailedLogins - 1) {
457 var title = lockscreen.alphaNumeric ?
458 i18n.tr("Sorry, incorrect passphrase.") :
459 i18n.tr("Sorry, incorrect passcode.")
460 var text = i18n.tr("This will be your last attempt.") + " " +
461 (lockscreen.alphaNumeric ?
462 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
463 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
464 lockscreen.showInfoPopup(title, text)
465 } else if (AccountsService.failedLogins >= maxFailedLogins) {
466 SystemImage.factoryReset() // Ouch!
469 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
470 lockscreen.delayMinutes = failedLoginsDelayMinutes
471 forcedDelayTimer.start()
474 lockscreen.clear(true);
475 if (greeter.narrowMode) {
476 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
483 target: LightDM.Greeter
485 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
491 opacity: greeterWrapper.showProgress * 0.8
495 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
497 objectName: "greeterWrapper"
498 x: greeter.narrowMode ? launcher.progress : 0
501 height: parent.height - panel.panelHeight
504 enabled: !launcher.dashSwipe
508 property bool fullyShown: showProgress === 1.0
509 onFullyShownChanged: {
510 // Wait until the greeter is completely covering lockscreen before resetting it.
511 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
513 lockscreen.maybeShow();
517 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
518 onShowProgressChanged: {
519 if (showProgress === 0) {
520 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
522 } else if (greeter.narrowMode) {
523 lockscreen.clear(false) // to reset focus if necessary
530 objectName: "greeter"
532 signal sessionStarted() // helpful for tests
534 property string lockedApp: ""
535 property bool hasLockedApp: lockedApp !== ""
538 hides: [launcher, panel.indicators]
540 loadContent: required || lockscreen.required // keeps content in memory for quick show()
544 background: shell.background
547 height: parent.height
549 dragHandleWidth: shell.edgeSize
551 function startUnlock() {
553 if (!LightDM.Greeter.authenticated) {
554 lockscreen.maybeShow()
565 if (LightDM.Greeter.startSessionSync()) {
576 // Disable everything so that user can't swipe greeter or
577 // launcher until we get first prompt/authenticate, which
578 // will re-enable the shell.
579 shell.enabled = false;
581 if (greeter.narrowMode) {
582 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
586 greeter.lockedApp = "";
587 greeter.forceActiveFocus();
591 Component.onCompleted: {
592 Connectivity.unlockAllModems()
595 onUnlocked: greeter.hide()
597 // Update launcher items for new user
598 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
599 AccountsService.user = user;
600 LauncherModel.setUser(user);
603 onTease: launcher.tease()
606 target: ApplicationManager
607 property: "suspended"
608 value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
618 if (shell.locked && callManager.hasCalls) {
619 // We just received an incoming call while locked. The
620 // indicator will have already launched dialer-app for us, but
621 // there is a race between "hasCalls" changing and the dialer
622 // starting up. So in case we lose that race, we'll start/
623 // focus the dialer ourselves here too. Even if the indicator
624 // didn't launch the dialer for some reason (or maybe a call
625 // started via some other means), if an active call is
626 // happening, we want to be in the dialer.
627 startLockedApp("dialer-app")
637 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
638 !callManager.hasCalls && !edgeDemo.running) {
644 function showHome() {
645 if (edgeDemo.running) {
649 if (LightDM.Greeter.active) {
650 greeter.startUnlock()
653 var animate = !LightDM.Greeter.active && !stages.shown
654 dash.setCurrentScope(0, animate, false)
655 ApplicationManager.requestFocusApplication("unity8-dash")
658 function showDash() {
659 if (greeter.hasLockedApp || // just in case user gets here
660 (!greeter.narrowMode && shell.locked)) {
669 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
670 ApplicationManager.requestFocusApplication("unity8-dash")
684 anchors.fill: parent //because this draws indicator menus
687 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
688 contentEnabled: edgeDemo.panelContentEnabled
689 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
691 minimizedPanelHeight: units.gu(3)
692 expandedPanelHeight: units.gu(7)
694 indicatorsModel: visibleIndicators.model
698 id: visibleIndicators
699 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
700 Component.onCompleted: initialise(indicatorProfile)
703 greeterShown: greeter.shown || lockscreen.shown
706 property bool topmostApplicationIsFullscreen:
707 ApplicationManager.focusedApplicationId &&
708 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
710 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
711 || greeter.hasLockedApp
716 objectName: "launcher"
718 readonly property bool dashSwipe: progress > 0
720 anchors.top: parent.top
721 anchors.bottom: parent.bottom
723 dragAreaWidth: shell.edgeSize
724 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
726 onShowDashHome: showHome()
728 onDashSwipeChanged: {
730 dash.setCurrentScope(0, false, true)
733 onLauncherApplicationSelected: {
734 if (greeter.hasLockedApp) {
735 greeter.startUnlock()
737 if (!edgeDemo.running)
738 shell.activateApplication(appId)
742 panel.indicators.hide()
748 id: modalNotificationBackground
750 visible: notifications.useModal && (notifications.state == "narrow")
763 model: NotificationBackend.Model
766 y: topmostIsFullscreen ? 0 : panel.panelHeight
768 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
773 when: overlay.width <= units.gu(60)
774 AnchorChanges { target: notifications; anchors.left: parent.left }
778 when: overlay.width > units.gu(60)
779 AnchorChanges { target: notifications; anchors.left: undefined }
780 PropertyChanges { target: notifications; width: units.gu(38) }
791 shutdownFadeOutRectangle.enabled = true;
792 shutdownFadeOutRectangle.visible = true;
793 shutdownFadeOut.start();
799 objectName: "edgeDemo"
801 paused: Powerd.status === Powerd.Off // Saves power
809 target: SessionBroadcast
810 onShowHome: showHome()
814 id: shutdownFadeOutRectangle
821 NumberAnimation on opacity {
826 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
827 DBusUnitySessionService.Shutdown();