on 5e571c9 help sign in
unity8/qml/Shell.qml
736 lines 25 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 "Panel/Indicators"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
44Item {
45 id: shell
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
59 property bool sideStageEnabled: shell.width >= units.gu(100)
60 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
62 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
63 property int failedLoginsDelayAttempts: 7 // number of failed logins
64 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
66 property int orientation
67 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
68 onDeviceOrientationAngleChanged: {
69 if (!OrientationLock.enabled) {
70 orientation = Screen.orientation;
71 }
72 }
73 readonly property bool orientationLockEnabled: OrientationLock.enabled
74 onOrientationLockEnabledChanged: {
75 if (orientationLockEnabled) {
76 OrientationLock.savedOrientation = Screen.orientation;
77 } else {
78 orientation = Screen.orientation;
79 }
80 }
82 function activateApplication(appId) {
83 if (ApplicationManager.findApplication(appId)) {
84 ApplicationManager.requestFocusApplication(appId);
85 } else {
86 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
87 ApplicationManager.startApplication(appId, execFlags);
88 }
89 }
91 function setFakeActiveForApp(app) {
92 if (shell.locked) {
93 greeter.fakeActiveForApp = app
94 lockscreen.hide()
95 }
96 }
98 Binding {
99 target: LauncherModel
100 property: "applicationManager"
101 value: ApplicationManager
102 }
104 Component.onCompleted: {
105 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
106 if (ApplicationManager.count > 0) {
107 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
108 }
109 if (orientationLockEnabled) {
110 orientation = OrientationLock.savedOrientation;
111 }
112 }
114 GSettings {
115 id: backgroundSettings
116 schema.id: "org.gnome.desktop.background"
117 }
118 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
119 onGSettingsPictureChanged: {
120 shell.background = gSettingsPicture
121 }
123 VolumeControl {
124 id: volumeControl
125 }
127 DashCommunicator {
128 id: dash
129 objectName: "dashCommunicator"
130 }
132 WindowKeysFilter {
133 // Handle but do not filter out volume keys
134 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
135 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
137 Keys.onPressed: {
138 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
139 dialogs.onPowerKeyPressed();
140 event.accepted = true;
141 } else {
142 event.accepted = false;
143 }
144 }
146 Keys.onReleased: {
147 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
148 dialogs.onPowerKeyReleased();
149 event.accepted = true;
150 } else {
151 event.accepted = false;
152 }
153 }
154 }
156 Item {
157 id: stages
158 objectName: "stages"
159 width: parent.width
160 height: parent.height
161 visible: !ApplicationManager.empty
163 Connections {
164 target: ApplicationManager
165 onFocusRequested: {
166 if (appId === "dialer-app") {
167 // Always let the dialer-app through. Either user asked
168 // for an emergency call or accepted an incoming call.
169 setFakeActiveForApp(appId)
170 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
171 lockscreen.show();
172 }
173 greeter.hide();
174 launcher.hide();
175 }
177 onFocusedApplicationIdChanged: {
178 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
179 lockscreen.show();
180 }
181 panel.indicators.hide();
182 }
184 onApplicationAdded: {
185 if (greeter.shown && appId != "unity8-dash") {
186 greeter.hide();
187 }
188 if (appId === "dialer-app") {
189 // Always let the dialer-app through. Either user asked
190 // for an emergency call or accepted an incoming call.
191 setFakeActiveForApp(appId)
192 }
193 launcher.hide();
194 }
195 }
197 Loader {
198 id: applicationsDisplayLoader
199 anchors.fill: parent
201 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
203 Binding {
204 target: applicationsDisplayLoader.item
205 property: "objectName"
206 value: "stage"
207 }
208 Binding {
209 target: applicationsDisplayLoader.item
210 property: "dragAreaWidth"
211 value: shell.edgeSize
212 }
213 Binding {
214 target: applicationsDisplayLoader.item
215 property: "maximizedAppTopMargin"
216 // Not just using panel.panelHeight as that changes depending on the focused app.
217 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
218 }
219 Binding {
220 target: applicationsDisplayLoader.item
221 property: "interactive"
222 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
223 }
224 Binding {
225 target: applicationsDisplayLoader.item
226 property: "spreadEnabled"
227 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
228 }
229 Binding {
230 target: applicationsDisplayLoader.item
231 property: "inverseProgress"
232 value: launcher.progress
233 }
234 Binding {
235 target: applicationsDisplayLoader.item
236 property: "orientation"
237 value: shell.orientation
238 }
239 }
240 }
242 InputMethod {
243 id: inputMethod
244 objectName: "inputMethod"
245 anchors { fill: parent; topMargin: panel.panelHeight }
246 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
247 }
249 Connections {
250 target: SurfaceManager
251 onSurfaceCreated: {
252 if (surface.type == MirSurfaceItem.InputMethod) {
253 inputMethod.surface = surface;
254 }
255 }
257 onSurfaceDestroyed: {
258 if (inputMethod.surface == surface) {
259 inputMethod.surface = null;
260 surface.parent = null;
261 }
262 if (!surface.parent) {
263 // there's no one displaying it. delete it right away
264 surface.release();
265 }
266 }
267 }
268 Connections {
269 target: SessionManager
270 onSessionStopping: {
271 if (!session.parentSession && !session.application) {
272 // nothing is using it. delete it right away
273 session.release();
274 }
275 }
276 }
278 Lockscreen {
279 id: lockscreen
280 objectName: "lockscreen"
282 hides: [launcher, panel.indicators]
283 shown: false
284 enabled: true
285 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
286 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
287 y: panel.panelHeight
288 visible: required
289 width: parent.width
290 height: parent.height - panel.panelHeight
291 background: shell.background
292 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
293 minPinLength: 4
294 maxPinLength: 4
296 // FIXME: We *should* show emergency dialer if there is a SIM present,
297 // regardless of whether the side stage is enabled. But right now,
298 // the assumption is that narrow screens are phones which have SIMs
299 // and wider screens are tablets which don't. When we do allow this
300 // on devices with a side stage and a SIM, work should be done to
301 // ensure that the main stage is disabled while the dialer is present
302 // in the side stage.
303 showEmergencyCallButton: !shell.sideStageEnabled
305 onEntered: LightDM.Greeter.respond(passphrase);
306 onCancel: greeter.show()
307 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
309 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
311 Timer {
312 id: forcedDelayTimer
313 interval: 1000 * 60
314 onTriggered: {
315 if (lockscreen.delayMinutes > 0) {
316 lockscreen.delayMinutes -= 1
317 if (lockscreen.delayMinutes > 0) {
318 start() // go again
319 }
320 }
321 }
322 }
324 Component.onCompleted: {
325 if (greeter.narrowMode) {
326 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
327 }
328 }
329 }
331 Connections {
332 target: LightDM.Greeter
334 onShowGreeter: greeter.show()
335 onHideGreeter: greeter.login()
337 onShowPrompt: {
338 if (greeter.narrowMode) {
339 if (isDefaultPrompt) {
340 if (lockscreen.alphaNumeric) {
341 lockscreen.infoText = i18n.tr("Enter passphrase")
342 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
343 i18n.tr("Please re-enter")
344 } else {
345 lockscreen.infoText = i18n.tr("Enter passcode")
346 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
347 }
348 } else {
349 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
350 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
351 }
353 lockscreen.show();
354 }
355 }
357 onPromptlessChanged: {
358 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
359 lockscreen.hide()
360 } else {
361 lockscreen.reset();
362 lockscreen.show();
363 }
364 }
366 onAuthenticationComplete: {
367 if (LightDM.Greeter.authenticated) {
368 AccountsService.failedLogins = 0
369 }
370 // Else only penalize user for a failed login if they actually were
371 // prompted for a password. We do this below after the promptless
372 // early exit.
374 if (LightDM.Greeter.promptless) {
375 return;
376 }
378 if (LightDM.Greeter.authenticated) {
379 greeter.login();
380 } else {
381 AccountsService.failedLogins++
382 if (maxFailedLogins >= 2) { // require at least a warning
383 if (AccountsService.failedLogins === maxFailedLogins - 1) {
384 var title = lockscreen.alphaNumeric ?
385 i18n.tr("Sorry, incorrect passphrase.") :
386 i18n.tr("Sorry, incorrect passcode.")
387 var text = i18n.tr("This will be your last attempt.") + " " +
388 (lockscreen.alphaNumeric ?
389 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
390 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
391 lockscreen.showInfoPopup(title, text)
392 } else if (AccountsService.failedLogins >= maxFailedLogins) {
393 SystemImage.factoryReset() // Ouch!
394 }
395 }
396 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
397 lockscreen.delayMinutes = failedLoginsDelayMinutes
398 forcedDelayTimer.start()
399 }
401 lockscreen.clear(true);
402 if (greeter.narrowMode) {
403 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
404 }
405 }
406 }
407 }
409 Binding {
410 target: LightDM.Greeter
411 property: "active"
412 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
413 }
415 Rectangle {
416 anchors.fill: parent
417 color: "black"
418 opacity: greeterWrapper.showProgress * 0.8
419 }
421 Item {
422 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
423 id: greeterWrapper
424 x: launcher.progress
425 y: panel.panelHeight
426 width: parent.width
427 height: parent.height - panel.panelHeight
429 Behavior on x {
430 enabled: !launcher.dashSwipe
431 StandardAnimation {}
432 }
434 property bool fullyShown: showProgress === 1.0
435 onFullyShownChanged: {
436 // Wait until the greeter is completely covering lockscreen before resetting it.
437 if (fullyShown && !LightDM.Greeter.authenticated) {
438 lockscreen.reset();
439 lockscreen.show();
440 }
441 }
443 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
444 onShowProgressChanged: {
445 if (showProgress === 0) {
446 if (LightDM.Greeter.authenticated) {
447 greeter.login()
448 } else if (greeter.narrowMode) {
449 lockscreen.clear(false) // to reset focus if necessary
450 }
451 }
452 }
454 Greeter {
455 id: greeter
456 objectName: "greeter"
458 signal sessionStarted() // helpful for tests
460 property string fakeActiveForApp: ""
462 available: true
463 hides: [launcher, panel.indicators]
464 shown: true
465 loadContent: required || lockscreen.required // keeps content in memory for quick show()
467 defaultBackground: shell.background
469 width: parent.width
470 height: parent.height
472 dragHandleWidth: shell.edgeSize
474 function login() {
475 enabled = false;
476 if (LightDM.Greeter.startSessionSync()) {
477 sessionStarted();
478 greeter.hide();
479 lockscreen.hide();
480 launcher.hide();
481 }
482 enabled = true;
483 }
485 onShownChanged: {
486 if (shown) {
487 if (greeter.narrowMode) {
488 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
489 }
490 greeter.fakeActiveForApp = "";
491 greeter.forceActiveFocus();
492 }
493 }
495 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
496 Component.onCompleted: {
497 Connectivity.unlockAllModems()
498 } */
500 onUnlocked: greeter.hide()
501 onSelected: {
502 // Update launcher items for new user
503 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
504 AccountsService.user = user;
505 LauncherModel.setUser(user);
506 }
508 onTease: launcher.tease()
510 Binding {
511 target: ApplicationManager
512 property: "suspended"
513 value: greeter.shown && greeterWrapper.showProgress == 1
514 }
515 }
516 }
518 Connections {
519 id: powerConnection
520 target: Powerd
522 onStatusChanged: {
523 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
524 greeter.showNow()
525 }
526 }
527 }
529 function showHome() {
530 if (edgeDemo.running) {
531 return
532 }
534 if (LightDM.Greeter.active) {
535 if (!LightDM.Greeter.authenticated) {
536 lockscreen.show()
537 }
538 greeter.hide()
539 }
541 var animate = !LightDM.Greeter.active && !stages.shown
542 dash.setCurrentScope("clickscope", animate, false)
543 ApplicationManager.requestFocusApplication("unity8-dash")
544 }
546 function showDash() {
547 if (greeter.fakeActiveForApp !== "") { // just in case user gets here
548 return
549 }
551 if (greeter.shown) {
552 greeter.hideRight();
553 launcher.fadeOut();
554 }
556 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
557 ApplicationManager.requestFocusApplication("unity8-dash")
558 launcher.fadeOut();
559 }
560 }
562 Item {
563 id: overlay
564 z: 10
566 anchors.fill: parent
568 Panel {
569 id: panel
570 objectName: "panel"
571 anchors.fill: parent //because this draws indicator menus
572 indicators {
573 hides: [launcher]
574 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
575 contentEnabled: edgeDemo.panelContentEnabled
576 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
578 minimizedPanelHeight: units.gu(3)
579 expandedPanelHeight: units.gu(7)
581 indicatorsModel: visibleIndicators.model
582 }
584 VisibleIndicators {
585 id: visibleIndicators
586 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
587 Component.onCompleted: initialise(indicatorProfile)
588 }
590 property bool topmostApplicationIsFullscreen:
591 ApplicationManager.focusedApplicationId &&
592 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
594 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
595 || greeter.fakeActiveForApp !== ""
596 }
598 Launcher {
599 id: launcher
600 objectName: "launcher"
602 readonly property bool dashSwipe: progress > 0
604 anchors.top: parent.top
605 anchors.bottom: parent.bottom
606 width: parent.width
607 dragAreaWidth: shell.edgeSize
608 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
610 onShowDashHome: showHome()
611 onDash: {
612 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
613 showDash()
614 }
615 }
616 onDashSwipeChanged: {
617 if (dashSwipe) {
618 dash.setCurrentScope("clickscope", false, true)
619 }
620 }
621 onLauncherApplicationSelected: {
622 if (greeter.fakeActiveForApp !== "") {
623 lockscreen.show()
624 }
625 if (!edgeDemo.running)
626 shell.activateApplication(appId)
627 }
628 onShownChanged: {
629 if (shown) {
630 panel.indicators.hide()
631 }
632 }
633 }
635 Rectangle {
636 id: modalNotificationBackground
638 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
639 color: "#000000"
640 anchors.fill: parent
641 opacity: 0.9
643 MouseArea {
644 anchors.fill: parent
645 }
646 }
648 Notifications {
649 id: notifications
651 model: NotificationBackend.Model
652 margin: units.gu(1)
654 y: panel.panelHeight
655 width: parent.width
656 height: parent.height - panel.panelHeight
658 states: [
659 State {
660 name: "narrow"
661 when: overlay.width <= units.gu(60)
662 AnchorChanges { target: notifications; anchors.left: parent.left }
663 },
664 State {
665 name: "wide"
666 when: overlay.width > units.gu(60)
667 AnchorChanges { target: notifications; anchors.left: undefined }
668 PropertyChanges { target: notifications; width: units.gu(38) }
669 }
670 ]
671 }
672 }
674 Dialogs {
675 id: dialogs
676 anchors.fill: parent
677 z: overlay.z + 10
678 onPowerOffClicked: {
679 shutdownFadeOutRectangle.enabled = true;
680 shutdownFadeOutRectangle.visible = true;
681 shutdownFadeOut.start();
682 }
683 }
685 Label {
686 id: alphaDisclaimerLabel
687 anchors.centerIn: parent
688 visible: ApplicationManager.fake ? ApplicationManager.fake : false
689 z: dialogs.z + 10
690 text: "EARLY ALPHA\nNOT READY FOR USE"
691 color: "lightgrey"
692 opacity: 0.2
693 font.weight: Font.Black
694 horizontalAlignment: Text.AlignHCenter
695 verticalAlignment: Text.AlignVCenter
696 fontSizeMode: Text.Fit
697 rotation: -45
698 scale: Math.min(parent.width, parent.height) / width
699 }
701 EdgeDemo {
702 id: edgeDemo
703 z: alphaDisclaimerLabel.z + 10
704 paused: Powerd.status === Powerd.Off // Saves power
705 greeter: greeter
706 launcher: launcher
707 indicators: panel.indicators
708 stages: stages
709 }
711 Connections {
712 target: SessionBroadcast
713 onShowHome: showHome()
714 }
716 Rectangle {
717 id: shutdownFadeOutRectangle
718 z: edgeDemo.z + 10
719 enabled: false
720 visible: false
721 color: "black"
722 anchors.fill: parent
723 opacity: 0.0
724 NumberAnimation on opacity {
725 id: shutdownFadeOut
726 from: 0.0
727 to: 1.0
728 onStopped: {
729 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
730 DBusUnitySessionService.Shutdown();
731 }
732 }
733 }
734 }