on 399449f help sign in
unity8/qml/Shell.qml
755 lines 26 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 */
17import QtQuick 2.0
18import QtQuick.Window 2.0
19import AccountsService 0.1
20import GSettings 1.0
21import Unity.Application 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
29import Utils 0.1
30import LightDM 0.1 as LightDM
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stages"
39import Unity.Notifications 1.0 as NotificationBackend
40import Unity.Session 0.1
41import Unity.DashCommunicator 0.1
43Item {
44 id: shell
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;
72 }
73 }
74 readonly property bool orientationLockEnabled: OrientationLock.enabled
75 onOrientationLockEnabledChanged: {
76 if (orientationLockEnabled) {
77 OrientationLock.savedOrientation = Screen.orientation;
78 } else {
79 orientation = Screen.orientation;
80 }
81 }
83 function activateApplication(appId) {
84 if (ApplicationManager.findApplication(appId)) {
85 ApplicationManager.requestFocusApplication(appId);
86 } else {
87 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
88 ApplicationManager.startApplication(appId, execFlags);
89 }
90 }
92 function setLockedApp(app) {
93 if (shell.locked) {
94 greeter.lockedApp = app
95 lockscreen.hide()
96 }
97 }
99 Binding {
100 target: LauncherModel
101 property: "applicationManager"
102 value: ApplicationManager
103 }
105 Component.onCompleted: {
106 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
107 if (ApplicationManager.count > 0) {
108 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
109 }
110 if (orientationLockEnabled) {
111 orientation = OrientationLock.savedOrientation;
112 }
113 }
115 GSettings {
116 id: backgroundSettings
117 schema.id: "org.gnome.desktop.background"
118 }
119 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
120 onGSettingsPictureChanged: {
121 shell.background = gSettingsPicture
122 }
124 VolumeControl {
125 id: volumeControl
126 }
128 DashCommunicator {
129 id: dash
130 objectName: "dashCommunicator"
131 }
133 WindowKeysFilter {
134 // Handle but do not filter out volume keys
135 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
136 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
138 Keys.onPressed: {
139 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
140 dialogs.onPowerKeyPressed();
141 event.accepted = true;
142 } else {
143 event.accepted = false;
144 }
145 }
147 Keys.onReleased: {
148 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
149 dialogs.onPowerKeyReleased();
150 event.accepted = true;
151 } else {
152 event.accepted = false;
153 }
154 }
155 }
157 Item {
158 id: stages
159 objectName: "stages"
160 width: parent.width
161 height: parent.height
162 visible: !ApplicationManager.empty
164 Connections {
165 target: ApplicationManager
166 onFocusRequested: {
167 if (greeter.narrowMode) {
168 if (appId === "dialer-app") {
169 // Always let the dialer-app through. Either user asked
170 // for an emergency call or accepted an incoming call.
171 setLockedApp(appId)
172 } else if (greeter.hasLockedApp && greeter.lockedApp !== appId) {
173 greeter.startUnlock()
174 }
175 greeter.hide();
176 launcher.hide();
177 } else {
178 if (LightDM.Greeter.active) {
179 greeter.startUnlock()
180 }
181 }
182 }
184 onFocusedApplicationIdChanged: {
185 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
186 greeter.startUnlock()
187 }
188 panel.indicators.hide();
189 }
191 onApplicationAdded: {
192 if (greeter.shown && appId != "unity8-dash") {
193 greeter.startUnlock()
194 }
195 if (greeter.narrowMode && appId === "dialer-app") {
196 // Always let the dialer-app through. Either user asked
197 // for an emergency call or accepted an incoming call.
198 setLockedApp(appId)
199 }
200 launcher.hide();
201 }
202 }
204 Loader {
205 id: applicationsDisplayLoader
206 objectName: "applicationsDisplayLoader"
207 anchors.fill: parent
209 // When we have a locked app, we only want to show that one app
210 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
211 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
213 Binding {
214 target: applicationsDisplayLoader.item
215 property: "objectName"
216 value: "stage"
217 }
218 Binding {
219 target: applicationsDisplayLoader.item
220 property: "dragAreaWidth"
221 value: shell.edgeSize
222 }
223 Binding {
224 target: applicationsDisplayLoader.item
225 property: "maximizedAppTopMargin"
226 // Not just using panel.panelHeight as that changes depending on the focused app.
227 value: panel.indicators.panelHeight
228 }
229 Binding {
230 target: applicationsDisplayLoader.item
231 property: "interactive"
232 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
233 }
234 Binding {
235 target: applicationsDisplayLoader.item
236 property: "spreadEnabled"
237 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
238 }
239 Binding {
240 target: applicationsDisplayLoader.item
241 property: "inverseProgress"
242 value: launcher.progress
243 }
244 Binding {
245 target: applicationsDisplayLoader.item
246 property: "orientation"
247 value: shell.orientation
248 }
249 }
250 }
252 InputMethod {
253 id: inputMethod
254 objectName: "inputMethod"
255 anchors { fill: parent; topMargin: panel.panelHeight }
256 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
257 }
259 Connections {
260 target: SurfaceManager
261 onSurfaceCreated: {
262 if (surface.type == MirSurfaceItem.InputMethod) {
263 inputMethod.surface = surface;
264 }
265 }
267 onSurfaceDestroyed: {
268 if (inputMethod.surface == surface) {
269 inputMethod.surface = null;
270 surface.parent = null;
271 }
272 if (!surface.parent) {
273 // there's no one displaying it. delete it right away
274 surface.release();
275 }
276 }
277 }
278 Connections {
279 target: SessionManager
280 onSessionStopping: {
281 if (!session.parentSession && !session.application) {
282 // nothing is using it. delete it right away
283 session.release();
284 }
285 }
286 }
288 Lockscreen {
289 id: lockscreen
290 objectName: "lockscreen"
292 hides: [launcher, panel.indicators]
293 shown: false
294 enabled: true
295 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
296 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
297 y: panel.panelHeight
298 visible: required
299 width: parent.width
300 height: parent.height - panel.panelHeight
301 background: shell.background
302 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
303 minPinLength: 4
304 maxPinLength: 4
306 // FIXME: We *should* show emergency dialer if there is a SIM present,
307 // regardless of whether the side stage is enabled. But right now,
308 // the assumption is that narrow screens are phones which have SIMs
309 // and wider screens are tablets which don't. When we do allow this
310 // on devices with a side stage and a SIM, work should be done to
311 // ensure that the main stage is disabled while the dialer is present
312 // in the side stage.
313 showEmergencyCallButton: !shell.sideStageEnabled
315 onEntered: LightDM.Greeter.respond(passphrase);
316 onCancel: greeter.show()
317 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter locked-app mode
319 onShownChanged: if (shown) greeter.lockedApp = ""
321 function maybeShow() {
322 if (!shell.forcedUnlock) {
323 show()
324 }
325 }
327 Timer {
328 id: forcedDelayTimer
329 interval: 1000 * 60
330 onTriggered: {
331 if (lockscreen.delayMinutes > 0) {
332 lockscreen.delayMinutes -= 1
333 if (lockscreen.delayMinutes > 0) {
334 start() // go again
335 }
336 }
337 }
338 }
340 Component.onCompleted: {
341 if (greeter.narrowMode) {
342 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
343 }
344 }
345 }
347 Connections {
348 target: LightDM.Greeter
350 onShowGreeter: greeter.show()
351 onHideGreeter: greeter.login()
353 onShowPrompt: {
354 if (greeter.narrowMode) {
355 if (isDefaultPrompt) {
356 if (lockscreen.alphaNumeric) {
357 lockscreen.infoText = i18n.tr("Enter passphrase")
358 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
359 i18n.tr("Please re-enter")
360 } else {
361 lockscreen.infoText = i18n.tr("Enter passcode")
362 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
363 }
364 } else {
365 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
366 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
367 }
369 lockscreen.maybeShow();
370 }
371 }
373 onPromptlessChanged: {
374 if (greeter.narrowMode) {
375 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
376 lockscreen.hide()
377 } else {
378 lockscreen.reset();
379 lockscreen.maybeShow();
380 }
381 }
382 }
384 onAuthenticationComplete: {
385 if (LightDM.Greeter.authenticated) {
386 AccountsService.failedLogins = 0
387 }
388 // Else only penalize user for a failed login if they actually were
389 // prompted for a password. We do this below after the promptless
390 // early exit.
392 if (LightDM.Greeter.promptless) {
393 return;
394 }
396 if (LightDM.Greeter.authenticated) {
397 greeter.login();
398 } else {
399 AccountsService.failedLogins++
400 if (maxFailedLogins >= 2) { // require at least a warning
401 if (AccountsService.failedLogins === maxFailedLogins - 1) {
402 var title = lockscreen.alphaNumeric ?
403 i18n.tr("Sorry, incorrect passphrase.") :
404 i18n.tr("Sorry, incorrect passcode.")
405 var text = i18n.tr("This will be your last attempt.") + " " +
406 (lockscreen.alphaNumeric ?
407 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
408 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
409 lockscreen.showInfoPopup(title, text)
410 } else if (AccountsService.failedLogins >= maxFailedLogins) {
411 SystemImage.factoryReset() // Ouch!
412 }
413 }
414 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
415 lockscreen.delayMinutes = failedLoginsDelayMinutes
416 forcedDelayTimer.start()
417 }
419 lockscreen.clear(true);
420 if (greeter.narrowMode) {
421 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
422 }
423 }
424 }
425 }
427 Binding {
428 target: LightDM.Greeter
429 property: "active"
430 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
431 }
433 Rectangle {
434 anchors.fill: parent
435 color: "black"
436 opacity: greeterWrapper.showProgress * 0.8
437 }
439 Item {
440 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
441 id: greeterWrapper
442 x: greeter.narrowMode ? launcher.progress : 0
443 y: panel.panelHeight
444 width: parent.width
445 height: parent.height - panel.panelHeight
447 Behavior on x {
448 enabled: !launcher.dashSwipe
449 StandardAnimation {}
450 }
452 property bool fullyShown: showProgress === 1.0
453 onFullyShownChanged: {
454 // Wait until the greeter is completely covering lockscreen before resetting it.
455 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
456 lockscreen.reset();
457 lockscreen.maybeShow();
458 }
459 }
461 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
462 onShowProgressChanged: {
463 if (showProgress === 0) {
464 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
465 greeter.login()
466 } else if (greeter.narrowMode) {
467 lockscreen.clear(false) // to reset focus if necessary
468 }
469 }
470 }
472 Greeter {
473 id: greeter
474 objectName: "greeter"
476 signal sessionStarted() // helpful for tests
478 property string lockedApp: ""
479 property bool hasLockedApp: lockedApp !== ""
481 available: true
482 hides: [launcher, panel.indicators]
483 shown: true
484 loadContent: required || lockscreen.required // keeps content in memory for quick show()
486 locked: shell.locked
488 defaultBackground: shell.background
490 width: parent.width
491 height: parent.height
493 dragHandleWidth: shell.edgeSize
495 function startUnlock() {
496 if (narrowMode) {
497 if (!LightDM.Greeter.authenticated) {
498 lockscreen.maybeShow()
499 }
500 hide()
501 } else {
502 show()
503 tryToUnlock()
504 }
505 }
507 function login() {
508 enabled = false;
509 if (LightDM.Greeter.startSessionSync()) {
510 sessionStarted();
511 greeter.hide();
512 lockscreen.hide();
513 launcher.hide();
514 }
515 enabled = true;
516 }
518 onShownChanged: {
519 if (shown) {
520 if (greeter.narrowMode) {
521 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
522 } else {
523 reset()
524 }
525 greeter.lockedApp = "";
526 greeter.forceActiveFocus();
527 }
528 }
530 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
531 Component.onCompleted: {
532 Connectivity.unlockAllModems()
533 } */
535 onUnlocked: greeter.hide()
536 onSelected: {
537 // Update launcher items for new user
538 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
539 AccountsService.user = user;
540 LauncherModel.setUser(user);
541 }
543 onTease: launcher.tease()
545 Binding {
546 target: ApplicationManager
547 property: "suspended"
548 value: greeter.shown && greeterWrapper.showProgress == 1
549 }
550 }
551 }
553 Connections {
554 id: powerConnection
555 target: Powerd
557 onStatusChanged: {
558 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
559 greeter.showNow()
560 }
561 }
562 }
564 function showHome() {
565 if (edgeDemo.running) {
566 return
567 }
569 if (LightDM.Greeter.active) {
570 greeter.startUnlock()
571 }
573 var animate = !LightDM.Greeter.active && !stages.shown
574 dash.setCurrentScope("clickscope", animate, false)
575 ApplicationManager.requestFocusApplication("unity8-dash")
576 }
578 function showDash() {
579 if (greeter.hasLockedApp) { // just in case user gets here
580 return
581 }
583 if (greeter.shown) {
584 greeter.hideRight();
585 launcher.fadeOut();
586 }
588 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
589 ApplicationManager.requestFocusApplication("unity8-dash")
590 launcher.fadeOut();
591 }
592 }
594 Item {
595 id: overlay
596 z: 10
598 anchors.fill: parent
600 Panel {
601 id: panel
602 objectName: "panel"
603 anchors.fill: parent //because this draws indicator menus
604 indicators {
605 hides: [launcher]
606 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
607 contentEnabled: edgeDemo.panelContentEnabled
608 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
609 panelHeight: units.gu(3)
610 }
612 property bool topmostApplicationIsFullscreen:
613 ApplicationManager.focusedApplicationId &&
614 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
616 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
617 || greeter.hasLockedApp
618 }
620 Launcher {
621 id: launcher
622 objectName: "launcher"
624 readonly property bool dashSwipe: progress > 0
626 anchors.top: parent.top
627 anchors.bottom: parent.bottom
628 width: parent.width
629 dragAreaWidth: shell.edgeSize
630 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
632 onShowDashHome: showHome()
633 onDash: showDash()
634 onDashSwipeChanged: {
635 if (dashSwipe) {
636 dash.setCurrentScope("clickscope", false, true)
637 }
638 }
639 onLauncherApplicationSelected: {
640 if (greeter.hasLockedApp) {
641 greeter.startUnlock()
642 }
643 if (!edgeDemo.running)
644 shell.activateApplication(appId)
645 }
646 onShownChanged: {
647 if (shown) {
648 panel.indicators.hide()
649 }
650 }
651 }
653 Rectangle {
654 id: modalNotificationBackground
656 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
657 color: "#000000"
658 anchors.fill: parent
659 opacity: 0.9
661 MouseArea {
662 anchors.fill: parent
663 }
664 }
666 Notifications {
667 id: notifications
669 model: NotificationBackend.Model
670 margin: units.gu(1)
672 y: panel.panelHeight
673 width: parent.width
674 height: parent.height - panel.panelHeight
676 states: [
677 State {
678 name: "narrow"
679 when: overlay.width <= units.gu(60)
680 AnchorChanges { target: notifications; anchors.left: parent.left }
681 },
682 State {
683 name: "wide"
684 when: overlay.width > units.gu(60)
685 AnchorChanges { target: notifications; anchors.left: undefined }
686 PropertyChanges { target: notifications; width: units.gu(38) }
687 }
688 ]
689 }
690 }
692 Dialogs {
693 id: dialogs
694 anchors.fill: parent
695 z: overlay.z + 10
696 onPowerOffClicked: {
697 shutdownFadeOutRectangle.enabled = true;
698 shutdownFadeOutRectangle.visible = true;
699 shutdownFadeOut.start();
700 }
701 }
703 Label {
704 id: alphaDisclaimerLabel
705 anchors.centerIn: parent
706 visible: ApplicationManager.fake ? ApplicationManager.fake : false
707 z: dialogs.z + 10
708 text: "EARLY ALPHA\nNOT READY FOR USE"
709 color: "lightgrey"
710 opacity: 0.2
711 font.weight: Font.Black
712 horizontalAlignment: Text.AlignHCenter
713 verticalAlignment: Text.AlignVCenter
714 fontSizeMode: Text.Fit
715 rotation: -45
716 scale: Math.min(parent.width, parent.height) / width
717 }
719 EdgeDemo {
720 id: edgeDemo
721 objectName: "edgeDemo"
722 z: alphaDisclaimerLabel.z + 10
723 paused: Powerd.status === Powerd.Off // Saves power
724 greeter: greeter
725 launcher: launcher
726 indicators: panel.indicators
727 stages: stages
728 }
730 Connections {
731 target: SessionBroadcast
732 onShowHome: showHome()
733 }
735 Rectangle {
736 id: shutdownFadeOutRectangle
737 z: edgeDemo.z + 10
738 enabled: false
739 visible: false
740 color: "black"
741 anchors.fill: parent
742 opacity: 0.0
743 NumberAnimation on opacity {
744 id: shutdownFadeOut
745 from: 0.0
746 to: 1.0
747 onStopped: {
748 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
749 DBusUnitySessionService.Shutdown();
750 }
751 }
752 }
753 }