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
55 readonly property real panelHeight: panel.panelHeight
57 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
58 readonly property bool forcedUnlock: edgeDemo.running
59 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
61 property bool sideStageEnabled: shell.width >= units.gu(100)
62 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
64 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
65 property int failedLoginsDelayAttempts: 7 // number of failed logins
66 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
68 property int orientation
69 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
70 onDeviceOrientationAngleChanged: {
71 if (!OrientationLock.enabled) {
72 orientation = Screen.orientation;
75 readonly property bool orientationLockEnabled: OrientationLock.enabled
76 onOrientationLockEnabledChanged: {
77 if (orientationLockEnabled) {
78 OrientationLock.savedOrientation = Screen.orientation;
80 orientation = Screen.orientation;
84 function activateApplication(appId) {
85 if (ApplicationManager.findApplication(appId)) {
86 ApplicationManager.requestFocusApplication(appId);
88 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
89 ApplicationManager.startApplication(appId, execFlags);
93 function startLockedApp(app) {
95 console.warn("Called startLockedApp(%1) when not locked, ignoring".arg(app))
98 greeter.lockedApp = app
99 shell.activateApplication(app)
103 target: LauncherModel
104 property: "applicationManager"
105 value: ApplicationManager
108 Component.onCompleted: {
109 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
110 if (ApplicationManager.count > 0) {
111 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
113 if (orientationLockEnabled) {
114 orientation = OrientationLock.savedOrientation;
119 id: backgroundSettings
120 schema.id: "org.gnome.desktop.background"
122 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
123 onGSettingsPictureChanged: {
124 shell.background = gSettingsPicture
133 objectName: "dashCommunicator"
137 target: ApplicationManager
138 property: "forceDashActive"
139 value: launcher.shown || launcher.dashSwipe
144 // Handle but do not filter out volume keys
145 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
146 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
149 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
150 dialogs.onPowerKeyPressed();
151 event.accepted = true;
153 event.accepted = false;
158 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
159 dialogs.onPowerKeyReleased();
160 event.accepted = true;
162 event.accepted = false;
171 height: parent.height
172 visible: !ApplicationManager.empty
175 target: ApplicationManager
177 if (greeter.narrowMode) {
178 if (appId === "dialer-app" && callManager.hasCalls) {
179 // If we are in the middle of a call, make dialer lockedApp and show it.
180 // This can happen if user backs out of dialer back to greeter, then
181 // launches dialer again.
182 greeter.lockedApp = appId;
184 if (greeter.hasLockedApp) {
185 if (appId === greeter.lockedApp) {
186 lockscreen.hide() // show locked app
188 greeter.startUnlock() // show lockscreen if necessary
193 if (LightDM.Greeter.active) {
194 greeter.startUnlock()
199 onFocusedApplicationIdChanged: {
200 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
201 greeter.startUnlock()
203 panel.indicators.hide();
206 onApplicationAdded: {
207 if (greeter.shown && appId != "unity8-dash") {
208 greeter.startUnlock()
210 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
211 lockscreen.hide() // show locked app
218 id: applicationsDisplayLoader
219 objectName: "applicationsDisplayLoader"
222 // When we have a locked app, we only want to show that one app.
223 // FIXME: do this in a less traumatic way. We currently only allow
224 // locked apps in phone mode (see FIXME in Lockscreen component in
225 // this same file). When that changes, we need to do something
226 // nicer here. But this code is currently just to prevent a
227 // theoretical attack where user enters lockedApp mode, then makes
228 // the screen larger (maybe connects to monitor) and tries to enter
230 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
231 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
234 target: applicationsDisplayLoader.item
235 property: "objectName"
239 target: applicationsDisplayLoader.item
240 property: "dragAreaWidth"
241 value: shell.edgeSize
244 target: applicationsDisplayLoader.item
245 property: "maximizedAppTopMargin"
246 // Not just using panel.panelHeight as that changes depending on the focused app.
247 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
250 target: applicationsDisplayLoader.item
251 property: "interactive"
252 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
255 target: applicationsDisplayLoader.item
256 property: "spreadEnabled"
257 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
260 target: applicationsDisplayLoader.item
261 property: "inverseProgress"
262 value: launcher.progress
265 target: applicationsDisplayLoader.item
266 property: "orientation"
267 value: shell.orientation
274 objectName: "inputMethod"
275 anchors { fill: parent; topMargin: panel.panelHeight }
276 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
280 target: SurfaceManager
282 if (surface.type == MirSurfaceItem.InputMethod) {
283 inputMethod.surface = surface;
287 onSurfaceDestroyed: {
288 if (inputMethod.surface == surface) {
289 inputMethod.surface = null;
290 surface.parent = null;
292 if (!surface.parent) {
293 // there's no one displaying it. delete it right away
299 target: SessionManager
301 if (!session.parentSession && !session.application) {
302 // nothing is using it. delete it right away
310 objectName: "lockscreen"
312 hides: [launcher, panel.indicators]
315 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
316 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320 height: parent.height - panel.panelHeight
321 background: shell.background
322 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
326 // FIXME: We *should* show emergency dialer if there is a SIM present,
327 // regardless of whether the side stage is enabled. But right now,
328 // the assumption is that narrow screens are phones which have SIMs
329 // and wider screens are tablets which don't. When we do allow this
330 // on devices with a side stage and a SIM, work should be done to
331 // ensure that the main stage is disabled while the dialer is present
332 // in the side stage. See the FIXME in the stage loader in this file.
333 showEmergencyCallButton: !shell.sideStageEnabled
335 onEntered: LightDM.Greeter.respond(passphrase);
336 onCancel: greeter.show()
337 onEmergencyCall: startLockedApp("dialer-app")
339 onShownChanged: if (shown) greeter.lockedApp = ""
341 function maybeShow() {
342 if (!shell.forcedUnlock) {
351 if (lockscreen.delayMinutes > 0) {
352 lockscreen.delayMinutes -= 1
353 if (lockscreen.delayMinutes > 0) {
360 Component.onCompleted: {
361 if (greeter.narrowMode) {
362 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
368 target: LightDM.Greeter
370 onShowGreeter: greeter.show()
371 onHideGreeter: greeter.login()
374 if (greeter.narrowMode) {
375 if (isDefaultPrompt) {
376 if (lockscreen.alphaNumeric) {
377 lockscreen.infoText = i18n.tr("Enter passphrase")
378 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
379 i18n.tr("Please re-enter")
381 lockscreen.infoText = i18n.tr("Enter passcode")
382 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
385 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
386 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
389 lockscreen.maybeShow();
393 onPromptlessChanged: {
394 if (greeter.narrowMode) {
395 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
399 lockscreen.maybeShow();
404 onAuthenticationComplete: {
405 if (LightDM.Greeter.authenticated) {
406 AccountsService.failedLogins = 0
408 // Else only penalize user for a failed login if they actually were
409 // prompted for a password. We do this below after the promptless
412 if (LightDM.Greeter.promptless) {
416 if (LightDM.Greeter.authenticated) {
419 AccountsService.failedLogins++
420 if (maxFailedLogins >= 2) { // require at least a warning
421 if (AccountsService.failedLogins === maxFailedLogins - 1) {
422 var title = lockscreen.alphaNumeric ?
423 i18n.tr("Sorry, incorrect passphrase.") :
424 i18n.tr("Sorry, incorrect passcode.")
425 var text = i18n.tr("This will be your last attempt.") + " " +
426 (lockscreen.alphaNumeric ?
427 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
428 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
429 lockscreen.showInfoPopup(title, text)
430 } else if (AccountsService.failedLogins >= maxFailedLogins) {
431 SystemImage.factoryReset() // Ouch!
434 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
435 lockscreen.delayMinutes = failedLoginsDelayMinutes
436 forcedDelayTimer.start()
439 lockscreen.clear(true);
440 if (greeter.narrowMode) {
441 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
448 target: LightDM.Greeter
450 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
456 opacity: greeterWrapper.showProgress * 0.8
460 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
462 objectName: "greeterWrapper"
463 x: greeter.narrowMode ? launcher.progress : 0
466 height: parent.height - panel.panelHeight
469 enabled: !launcher.dashSwipe
473 property bool fullyShown: showProgress === 1.0
474 onFullyShownChanged: {
475 // Wait until the greeter is completely covering lockscreen before resetting it.
476 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
478 lockscreen.maybeShow();
482 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
483 onShowProgressChanged: {
484 if (showProgress === 0) {
485 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
487 } else if (greeter.narrowMode) {
488 lockscreen.clear(false) // to reset focus if necessary
495 objectName: "greeter"
497 signal sessionStarted() // helpful for tests
499 property string lockedApp: ""
500 property bool hasLockedApp: lockedApp !== ""
503 hides: [launcher, panel.indicators]
505 loadContent: required || lockscreen.required // keeps content in memory for quick show()
509 defaultBackground: shell.background
512 height: parent.height
514 dragHandleWidth: shell.edgeSize
516 function startUnlock() {
518 if (!LightDM.Greeter.authenticated) {
519 lockscreen.maybeShow()
530 if (LightDM.Greeter.startSessionSync()) {
541 if (greeter.narrowMode) {
542 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
546 greeter.lockedApp = "";
547 greeter.forceActiveFocus();
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
650 minimizedPanelHeight: units.gu(3)
651 expandedPanelHeight: units.gu(7)
653 indicatorsModel: visibleIndicators.model
657 id: visibleIndicators
658 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
659 Component.onCompleted: initialise(indicatorProfile)
662 property bool topmostApplicationIsFullscreen:
663 ApplicationManager.focusedApplicationId &&
664 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
666 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
667 || greeter.hasLockedApp
672 objectName: "launcher"
674 readonly property bool dashSwipe: progress > 0
676 anchors.top: parent.top
677 anchors.bottom: parent.bottom
679 dragAreaWidth: shell.edgeSize
680 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
682 onShowDashHome: showHome()
684 onDashSwipeChanged: {
686 dash.setCurrentScope("clickscope", false, true)
689 onLauncherApplicationSelected: {
690 if (greeter.hasLockedApp) {
691 greeter.startUnlock()
693 if (!edgeDemo.running)
694 shell.activateApplication(appId)
698 panel.indicators.hide()
704 id: modalNotificationBackground
706 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
719 model: NotificationBackend.Model
722 y: topmostIsFullscreen ? 0 : panel.panelHeight
724 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
729 when: overlay.width <= units.gu(60)
730 AnchorChanges { target: notifications; anchors.left: parent.left }
734 when: overlay.width > units.gu(60)
735 AnchorChanges { target: notifications; anchors.left: undefined }
736 PropertyChanges { target: notifications; width: units.gu(38) }
747 shutdownFadeOutRectangle.enabled = true;
748 shutdownFadeOutRectangle.visible = true;
749 shutdownFadeOut.start();
754 id: alphaDisclaimerLabel
755 anchors.centerIn: parent
756 visible: ApplicationManager.fake ? ApplicationManager.fake : false
758 text: "EARLY ALPHA\nNOT READY FOR USE"
761 font.weight: Font.Black
762 horizontalAlignment: Text.AlignHCenter
763 verticalAlignment: Text.AlignVCenter
764 fontSizeMode: Text.Fit
766 scale: Math.min(parent.width, parent.height) / width
771 objectName: "edgeDemo"
772 z: alphaDisclaimerLabel.z + 10
773 paused: Powerd.status === Powerd.Off // Saves power
781 target: SessionBroadcast
782 onShowHome: showHome()
786 id: shutdownFadeOutRectangle
793 NumberAnimation on opacity {
798 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
799 DBusUnitySessionService.Shutdown();