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"
41 import Unity.Notifications 1.0 as NotificationBackend
42 import Unity.Session 0.1
43 import Unity.DashCommunicator 0.1
44 import Unity.Indicators 0.1 as Indicators
49 // Disable everything so that user can't swipe greeter or launcher until
50 // we get first prompt/authenticate, which will re-enable the shell.
53 // this is only here to select the width / height of the window if not running fullscreen
54 property bool tablet: false
55 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
56 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
58 property real edgeSize: units.gu(2)
59 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
60 property url background: asImageTester.status == Image.Ready ? asImageTester.source
61 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
62 readonly property real panelHeight: panel.panelHeight
64 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
65 readonly property alias hasLockedApp: greeter.hasLockedApp
66 readonly property bool forcedUnlock: tutorial.running
67 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
69 property bool sideStageEnabled: shell.width >= units.gu(100)
70 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
72 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
73 property int failedLoginsDelayAttempts: 7 // number of failed logins
74 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
76 property int orientation
77 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
78 onDeviceOrientationAngleChanged: {
79 if (!OrientationLock.enabled) {
80 orientation = Screen.orientation;
83 readonly property bool orientationLockEnabled: OrientationLock.enabled
84 onOrientationLockEnabledChanged: {
85 if (orientationLockEnabled) {
86 OrientationLock.savedOrientation = Screen.orientation;
88 orientation = Screen.orientation;
92 function activateApplication(appId) {
93 if (ApplicationManager.findApplication(appId)) {
94 ApplicationManager.requestFocusApplication(appId);
96 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
97 ApplicationManager.startApplication(appId, execFlags);
101 function startLockedApp(app) {
103 greeter.lockedApp = app;
105 shell.activateApplication(app);
108 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
111 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
119 id: backgroundSettings
120 schema.id: "org.gnome.desktop.background"
123 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
126 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
134 id: usageModeSettings
135 schema.id: "com.canonical.Unity8"
139 target: LauncherModel
140 property: "applicationManager"
141 value: ApplicationManager
144 Component.onCompleted: {
145 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
146 if (ApplicationManager.count > 0) {
147 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
149 if (orientationLockEnabled) {
150 orientation = OrientationLock.savedOrientation;
160 objectName: "dashCommunicator"
166 enabled: Powerd.status === Powerd.On
170 target: ApplicationManager
171 property: "forceDashActive"
172 value: launcher.shown || launcher.dashSwipe
177 onVolumeDownPressed: volumeControl.volumeDown()
178 onVolumeUpPressed: volumeControl.volumeUp()
179 onBothVolumeKeysPressed: screenGrabber.capture()
184 // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
185 if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
186 event.accepted = callManager.handleMediaKey(false);
187 } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
188 // FIXME: We only consider power key presses if the screen is
189 // on because of bugs 1410830/1409003. The theory is that when
190 // those bugs are encountered, there is a >2s delay between the
191 // power press event and the power release event, which causes
192 // the shutdown dialog to appear on resume. So to avoid that
193 // symptom while we investigate the root cause, we simply won't
194 // initiate any dialogs when the screen is off.
195 if (Powerd.status === Powerd.On) {
196 dialogs.onPowerKeyPressed();
198 event.accepted = true;
200 volumeKeyFilter.onKeyPressed(event.key);
201 event.accepted = false;
206 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
207 dialogs.onPowerKeyReleased();
208 event.accepted = true;
210 volumeKeyFilter.onKeyReleased(event.key);
211 event.accepted = false;
220 height: parent.height
221 visible: !ApplicationManager.empty
224 target: ApplicationManager
226 if (greeter.narrowMode) {
227 if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
228 // If we are in the middle of a call, make dialer lockedApp and show it.
229 // This can happen if user backs out of dialer back to greeter, then
230 // launches dialer again.
231 greeter.lockedApp = appId;
233 if (greeter.hasLockedApp) {
234 if (appId === greeter.lockedApp) {
235 lockscreen.hide() // show locked app
237 greeter.startUnlock() // show lockscreen if necessary
242 if (LightDM.Greeter.active) {
243 greeter.startUnlock()
248 onFocusedApplicationIdChanged: {
249 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
250 greeter.startUnlock()
252 panel.indicators.hide();
255 onApplicationAdded: {
256 if (appId != "unity8-dash") {
258 greeter.startUnlock();
261 // If this happens on first boot, we may be in edge
262 // tutorial or wizard while receiving a call. But a call
263 // is more important than wizard so just bail out of those.
264 if (tutorial.running) {
269 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
270 lockscreen.hide() // show locked app
277 id: applicationsDisplayLoader
278 objectName: "applicationsDisplayLoader"
281 // When we have a locked app, we only want to show that one app.
282 // FIXME: do this in a less traumatic way. We currently only allow
283 // locked apps in phone mode (see FIXME in Lockscreen component in
284 // this same file). When that changes, we need to do something
285 // nicer here. But this code is currently just to prevent a
286 // theoretical attack where user enters lockedApp mode, then makes
287 // the screen larger (maybe connects to monitor) and tries to enter
289 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
290 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
291 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
293 property bool interactive: tutorial.spreadEnabled
296 && panel.indicators.fullyClosed
297 && launcher.progress == 0
298 && !notifications.useModal
300 onInteractiveChanged: { if (interactive) { focus = true; } }
303 target: applicationsDisplayLoader.item
304 property: "objectName"
308 target: applicationsDisplayLoader.item
309 property: "dragAreaWidth"
310 value: shell.edgeSize
313 target: applicationsDisplayLoader.item
314 property: "maximizedAppTopMargin"
315 // Not just using panel.panelHeight as that changes depending on the focused app.
316 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
319 target: applicationsDisplayLoader.item
320 property: "interactive"
321 value: applicationsDisplayLoader.interactive
324 target: applicationsDisplayLoader.item
325 property: "spreadEnabled"
326 value: tutorial.spreadEnabled && !greeter.hasLockedApp
329 target: applicationsDisplayLoader.item
330 property: "inverseProgress"
331 value: launcher.progress
334 target: applicationsDisplayLoader.item
335 property: "orientation"
336 value: shell.orientation
339 target: applicationsDisplayLoader.item
340 property: "background"
341 value: shell.background
348 objectName: "inputMethod"
349 anchors { fill: parent; topMargin: panel.panelHeight }
350 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
354 target: SurfaceManager
356 if (surface.type == MirSurfaceItem.InputMethod) {
357 inputMethod.surface = surface;
361 onSurfaceDestroyed: {
362 if (inputMethod.surface == surface) {
363 inputMethod.surface = null;
364 surface.parent = null;
366 if (!surface.parent) {
367 // there's no one displaying it. delete it right away
373 target: SessionManager
375 if (!session.parentSession && !session.application) {
376 // nothing is using it. delete it right away
384 objectName: "lockscreen"
386 hides: [launcher, panel.indicators]
389 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
390 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
394 height: parent.height - panel.panelHeight
395 background: shell.background
396 darkenBackground: 0.4
397 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
401 property string promptText
402 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
403 alphaNumeric ? i18n.tr("Enter passphrase") :
404 i18n.tr("Enter passcode")
405 errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
406 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
407 i18n.tr("Please re-enter") :
408 i18n.tr("Sorry, incorrect passcode")
410 // FIXME: We *should* show emergency dialer if there is a SIM present,
411 // regardless of whether the side stage is enabled. But right now,
412 // the assumption is that narrow screens are phones which have SIMs
413 // and wider screens are tablets which don't. When we do allow this
414 // on devices with a side stage and a SIM, work should be done to
415 // ensure that the main stage is disabled while the dialer is present
416 // in the side stage. See the FIXME in the stage loader in this file.
417 showEmergencyCallButton: !shell.sideStageEnabled
419 onEntered: LightDM.Greeter.respond(passphrase);
420 onCancel: greeter.show()
421 onEmergencyCall: startLockedApp("dialer-app")
423 onShownChanged: if (shown) greeter.lockedApp = ""
425 function maybeShow() {
426 if (!shell.forcedUnlock && !shown) {
435 if (lockscreen.delayMinutes > 0) {
436 lockscreen.delayMinutes -= 1
437 if (lockscreen.delayMinutes > 0) {
444 Component.onCompleted: {
445 if (greeter.narrowMode) {
446 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
452 target: LightDM.Greeter
454 onShowGreeter: greeter.show()
455 onHideGreeter: greeter.login()
458 shell.enabled = true;
459 if (!LightDM.Greeter.active) {
460 return; // could happen if hideGreeter() comes in before we prompt
462 if (greeter.narrowMode) {
463 lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
464 lockscreen.maybeShow();
468 onPromptlessChanged: {
469 if (!LightDM.Greeter.active) {
470 return; // could happen if hideGreeter() comes in before we prompt
472 if (greeter.narrowMode) {
473 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
477 lockscreen.maybeShow();
482 onAuthenticationComplete: {
483 shell.enabled = true;
484 if (LightDM.Greeter.authenticated) {
485 AccountsService.failedLogins = 0
487 // Else only penalize user for a failed login if they actually were
488 // prompted for a password. We do this below after the promptless
491 if (LightDM.Greeter.promptless) {
495 if (LightDM.Greeter.authenticated) {
498 AccountsService.failedLogins++
499 if (maxFailedLogins >= 2) { // require at least a warning
500 if (AccountsService.failedLogins === maxFailedLogins - 1) {
501 var title = lockscreen.alphaNumeric ?
502 i18n.tr("Sorry, incorrect passphrase.") :
503 i18n.tr("Sorry, incorrect passcode.")
504 var text = i18n.tr("This will be your last attempt.") + " " +
505 (lockscreen.alphaNumeric ?
506 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
507 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
508 lockscreen.showInfoPopup(title, text)
509 } else if (AccountsService.failedLogins >= maxFailedLogins) {
510 SystemImage.factoryReset() // Ouch!
513 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
514 lockscreen.delayMinutes = failedLoginsDelayMinutes
515 forcedDelayTimer.start()
518 lockscreen.clear(true);
519 if (greeter.narrowMode) {
520 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
527 target: LightDM.Greeter
529 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
535 opacity: greeterWrapper.showProgress * 0.8
539 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
541 objectName: "greeterWrapper"
542 x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
545 height: parent.height - panel.panelHeight
548 enabled: !launcher.dashSwipe
552 property bool fullyShown: showProgress === 1.0
553 onFullyShownChanged: {
554 // Wait until the greeter is completely covering lockscreen before resetting it.
555 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
557 lockscreen.maybeShow();
561 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
562 onShowProgressChanged: {
563 if (showProgress === 0) {
564 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
566 } else if (greeter.narrowMode) {
567 lockscreen.clear(false) // to reset focus if necessary
574 objectName: "greeter"
576 signal sessionStarted() // helpful for tests
578 property string lockedApp: ""
579 property bool hasLockedApp: lockedApp !== ""
581 hides: [launcher, panel.indicators]
582 loadContent: required || lockscreen.required // keeps content in memory for quick show()
586 background: shell.background
589 height: parent.height
592 // avoid overlapping with Launcher's edge drag area
593 // FIXME: Fix TouchRegistry & friends and remove this workaround
594 // Issue involves launcher's DDA getting disabled on a long
596 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
598 function startUnlock() {
600 if (!LightDM.Greeter.authenticated) {
601 lockscreen.maybeShow()
612 if (LightDM.Greeter.startSessionSync()) {
622 // See powerConnection for why this is useful
623 id: showGreeterDelayed
632 // Disable everything so that user can't swipe greeter or
633 // launcher until we get the next prompt/authenticate, which
634 // will re-enable the shell.
635 shell.enabled = false;
637 if (greeter.narrowMode) {
638 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
642 greeter.lockedApp = "";
643 greeter.forceActiveFocus();
647 Component.onCompleted: {
648 Connectivity.unlockAllModems()
651 onUnlocked: greeter.hide()
653 // Update launcher items for new user
654 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
655 AccountsService.user = user;
656 LauncherModel.setUser(user);
660 if (!tutorial.running) {
665 if (dragging && !tutorial.running) {
671 target: ApplicationManager
672 property: "suspended"
673 value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
683 if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
684 // We just received an incoming call while locked. The
685 // indicator will have already launched dialer-app for us, but
686 // there is a race between "hasCalls" changing and the dialer
687 // starting up. So in case we lose that race, we'll start/
688 // focus the dialer ourselves here too. Even if the indicator
689 // didn't launch the dialer for some reason (or maybe a call
690 // started via some other means), if an active call is
691 // happening, we want to be in the dialer.
692 startLockedApp("dialer-app")
702 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
703 !callManager.hasCalls && !tutorial.running) {
704 // We don't want to simply call greeter.showNow() here, because
705 // that will take too long. Qt will delay button event
706 // handling until the greeter is done loading and may think the
707 // user held down the power button the whole time, leading to a
708 // power dialog being shown. Instead, delay showing the
709 // greeter until we've finished handling the event. We could
710 // make the greeter load asynchronously instead, but that
711 // introduces a whole host of timing issues, especially with
712 // its animations. So this is simpler.
713 showGreeterDelayed.start();
718 function showHome() {
719 if (tutorial.running) {
723 if (LightDM.Greeter.active) {
724 greeter.startUnlock()
727 var animate = !LightDM.Greeter.active && !stages.shown
728 dash.setCurrentScope(0, animate, false)
729 ApplicationManager.requestFocusApplication("unity8-dash")
732 function showDash() {
733 if (greeter.hasLockedApp || // just in case user gets here
734 (!greeter.narrowMode && shell.locked)) {
743 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
744 ApplicationManager.requestFocusApplication("unity8-dash")
758 anchors.fill: parent //because this draws indicator menus
761 available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
762 contentEnabled: tutorial.panelContentEnabled
763 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
765 minimizedPanelHeight: units.gu(3)
766 expandedPanelHeight: units.gu(7)
768 indicatorsModel: Indicators.IndicatorsModel {
769 // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
770 profile: indicatorProfile
771 Component.onCompleted: load()
775 greeterShown: greeter.shown || lockscreen.shown
778 property bool topmostApplicationIsFullscreen:
779 ApplicationManager.focusedApplicationId &&
780 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
782 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
783 || greeter.hasLockedApp
788 objectName: "launcher"
790 readonly property bool dashSwipe: progress > 0
792 anchors.top: parent.top
793 anchors.topMargin: inverted ? 0 : panel.panelHeight
794 anchors.bottom: parent.bottom
796 dragAreaWidth: shell.edgeSize
797 available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
798 inverted: usageModeSettings.usageMode === "Staged"
799 shadeBackground: !tutorial.running
801 onShowDashHome: showHome()
803 onDashSwipeChanged: {
805 dash.setCurrentScope(0, false, true)
808 onLauncherApplicationSelected: {
809 if (greeter.hasLockedApp) {
810 greeter.startUnlock()
812 if (!tutorial.running)
813 shell.activateApplication(appId)
817 panel.indicators.hide()
825 background: shell.background
829 id: modalNotificationBackground
831 visible: notifications.useModal && (notifications.state == "narrow")
844 model: NotificationBackend.Model
847 y: topmostIsFullscreen ? 0 : panel.panelHeight
848 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
853 when: overlay.width <= units.gu(60)
855 target: notifications
856 anchors.left: parent.left
857 anchors.right: parent.right
862 when: overlay.width > units.gu(60)
864 target: notifications
865 anchors.left: undefined
866 anchors.right: parent.right
868 PropertyChanges { target: notifications; width: units.gu(38) }
879 shutdownFadeOutRectangle.enabled = true;
880 shutdownFadeOutRectangle.visible = true;
881 shutdownFadeOut.start();
887 objectName: "tutorial"
888 active: AccountsService.demoEdges
889 paused: LightDM.Greeter.active
894 edgeSize: shell.edgeSize
897 AccountsService.demoEdges = false;
898 active = false; // for immediate response / if AS is having problems
903 target: SessionBroadcast
904 onShowHome: showHome()
908 id: shutdownFadeOutRectangle
909 z: screenGrabber.z + 10
915 NumberAnimation on opacity {
920 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
921 DBusUnitySessionService.Shutdown();