on 162c50f help sign in
unity8/qml/Shell.qml
681 lines 23 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 AccountsService 0.1
19import GSettings 1.0
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Components.Popups 1.0
23import Ubuntu.Gestures 0.1
24import Ubuntu.SystemImage 0.1
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Connectivity 0.1
27import Unity.Launcher 0.1
28import Utils 0.1
29import LightDM 0.1 as LightDM
30import Powerd 0.1
31import SessionBroadcast 0.1
32import "Greeter"
33import "Launcher"
34import "Panel"
35import "Components"
36import "Notifications"
37import "Stages"
38import Unity.Notifications 1.0 as NotificationBackend
39import Unity.Session 0.1
40import Unity.DashCommunicator 0.1
42Item {
43 id: shell
45 // this is only here to select the width / height of the window if not running fullscreen
46 property bool tablet: false
47 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
48 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
50 property real edgeSize: units.gu(2)
51 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
52 property url background
53 readonly property real panelHeight: panel.panelHeight
55 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
57 property bool sideStageEnabled: shell.width >= units.gu(100)
58 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
60 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
61 property int failedLoginsDelayAttempts: 7 // number of failed logins
62 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
64 function activateApplication(appId) {
65 if (ApplicationManager.findApplication(appId)) {
66 ApplicationManager.requestFocusApplication(appId);
67 } else {
68 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
69 ApplicationManager.startApplication(appId, execFlags);
70 }
71 }
73 function setFakeActiveForApp(app) {
74 if (shell.locked) {
75 greeter.fakeActiveForApp = app
76 lockscreen.hide()
77 }
78 }
80 Binding {
81 target: LauncherModel
82 property: "applicationManager"
83 value: ApplicationManager
84 }
86 Component.onCompleted: {
87 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
88 }
90 GSettings {
91 id: backgroundSettings
92 schema.id: "org.gnome.desktop.background"
93 }
94 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
95 onGSettingsPictureChanged: {
96 shell.background = gSettingsPicture
97 }
99 VolumeControl {
100 id: volumeControl
101 }
103 DashCommunicator {
104 id: dash
105 objectName: "dashCommunicator"
106 }
108 WindowKeysFilter {
109 // Handle but do not filter out volume keys
110 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
111 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
113 Keys.onPressed: {
114 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
115 dialogs.onPowerKeyPressed();
116 event.accepted = true;
117 } else {
118 event.accepted = false;
119 }
120 }
122 Keys.onReleased: {
123 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
124 dialogs.onPowerKeyReleased();
125 event.accepted = true;
126 } else {
127 event.accepted = false;
128 }
129 }
130 }
132 Item {
133 id: stages
134 objectName: "stages"
135 width: parent.width
136 height: parent.height
137 visible: !ApplicationManager.empty
139 Connections {
140 target: ApplicationManager
141 onFocusRequested: {
142 if (appId === "dialer-app") {
143 // Always let the dialer-app through. Either user asked
144 // for an emergency call or accepted an incoming call.
145 setFakeActiveForApp(appId)
146 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
147 lockscreen.show();
148 }
149 greeter.hide();
150 launcher.hide();
151 }
153 onFocusedApplicationIdChanged: {
154 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
155 lockscreen.show();
156 }
157 panel.indicators.hide();
158 }
160 onApplicationAdded: {
161 if (greeter.shown && appId != "unity8-dash") {
162 greeter.hide();
163 }
164 if (appId === "dialer-app") {
165 // Always let the dialer-app through. Either user asked
166 // for an emergency call or accepted an incoming call.
167 setFakeActiveForApp(appId)
168 }
169 launcher.hide();
170 }
171 }
173 Loader {
174 id: applicationsDisplayLoader
175 anchors.fill: parent
177 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
179 Binding {
180 target: applicationsDisplayLoader.item
181 property: "objectName"
182 value: "stage"
183 }
184 Binding {
185 target: applicationsDisplayLoader.item
186 property: "dragAreaWidth"
187 value: shell.edgeSize
188 }
189 Binding {
190 target: applicationsDisplayLoader.item
191 property: "maximizedAppTopMargin"
192 // Not just using panel.panelHeight as that changes depending on the focused app.
193 value: panel.indicators.panelHeight
194 }
195 Binding {
196 target: applicationsDisplayLoader.item
197 property: "interactive"
198 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
199 }
200 Binding {
201 target: applicationsDisplayLoader.item
202 property: "spreadEnabled"
203 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
204 }
205 Binding {
206 target: applicationsDisplayLoader.item
207 property: "inverseProgress"
208 value: launcher.progress
209 }
210 }
211 }
213 InputMethod {
214 id: inputMethod
215 objectName: "inputMethod"
216 anchors { fill: parent; topMargin: panel.panelHeight }
217 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
218 }
220 Connections {
221 target: SurfaceManager
222 onSurfaceCreated: {
223 if (surface.type == MirSurfaceItem.InputMethod) {
224 inputMethod.surface = surface;
225 }
226 }
228 onSurfaceDestroyed: {
229 if (inputMethod.surface == surface) {
230 inputMethod.surface = null;
231 surface.parent = null;
232 }
233 if (!surface.parent) {
234 // there's no one displaying it. delete it right away
235 surface.release();
236 }
237 }
238 }
239 Connections {
240 target: SessionManager
241 onSessionStopping: {
242 if (!session.parentSession && !session.application) {
243 // nothing is using it. delete it right away
244 session.release();
245 }
246 }
247 }
249 Lockscreen {
250 id: lockscreen
251 objectName: "lockscreen"
253 hides: [launcher, panel.indicators]
254 shown: false
255 enabled: true
256 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
257 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
258 y: panel.panelHeight
259 visible: required
260 width: parent.width
261 height: parent.height - panel.panelHeight
262 background: shell.background
263 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
264 minPinLength: 4
265 maxPinLength: 4
267 onEntered: LightDM.Greeter.respond(passphrase);
268 onCancel: greeter.show()
269 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
271 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
273 Timer {
274 id: forcedDelayTimer
275 interval: 1000 * 60
276 onTriggered: {
277 if (lockscreen.delayMinutes > 0) {
278 lockscreen.delayMinutes -= 1
279 if (lockscreen.delayMinutes > 0) {
280 start() // go again
281 }
282 }
283 }
284 }
286 Component.onCompleted: {
287 if (greeter.narrowMode) {
288 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
289 }
290 }
291 }
293 Connections {
294 target: LightDM.Greeter
296 onShowGreeter: greeter.show()
297 onHideGreeter: greeter.login()
299 onShowPrompt: {
300 if (greeter.narrowMode) {
301 if (isDefaultPrompt) {
302 if (lockscreen.alphaNumeric) {
303 lockscreen.infoText = i18n.tr("Enter passphrase")
304 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
305 i18n.tr("Please re-enter")
306 } else {
307 lockscreen.infoText = i18n.tr("Enter passcode")
308 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
309 }
310 } else {
311 lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
312 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
313 }
315 lockscreen.show();
316 }
317 }
319 onPromptlessChanged: {
320 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
321 lockscreen.hide()
322 } else {
323 lockscreen.reset();
324 lockscreen.show();
325 }
326 }
328 onAuthenticationComplete: {
329 if (LightDM.Greeter.authenticated) {
330 AccountsService.failedLogins = 0
331 }
332 // Else only penalize user for a failed login if they actually were
333 // prompted for a password. We do this below after the promptless
334 // early exit.
336 if (LightDM.Greeter.promptless) {
337 return;
338 }
340 if (LightDM.Greeter.authenticated) {
341 greeter.login();
342 } else {
343 AccountsService.failedLogins++
344 if (maxFailedLogins >= 2) { // require at least a warning
345 if (AccountsService.failedLogins === maxFailedLogins - 1) {
346 var title = lockscreen.alphaNumeric ?
347 i18n.tr("Sorry, incorrect passphrase.") :
348 i18n.tr("Sorry, incorrect passcode.")
349 var text = i18n.tr("This will be your last attempt.") + " " +
350 (lockscreen.alphaNumeric ?
351 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
352 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
353 lockscreen.showInfoPopup(title, text)
354 } else if (AccountsService.failedLogins >= maxFailedLogins) {
355 SystemImage.factoryReset() // Ouch!
356 }
357 }
358 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
359 lockscreen.delayMinutes = failedLoginsDelayMinutes
360 forcedDelayTimer.start()
361 }
363 lockscreen.clear(true);
364 if (greeter.narrowMode) {
365 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
366 }
367 }
368 }
369 }
371 Binding {
372 target: LightDM.Greeter
373 property: "active"
374 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
375 }
377 Rectangle {
378 anchors.fill: parent
379 color: "black"
380 opacity: greeterWrapper.showProgress * 0.8
381 }
383 Item {
384 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
385 id: greeterWrapper
386 x: launcher.progress
387 y: panel.panelHeight
388 width: parent.width
389 height: parent.height - panel.panelHeight
391 Behavior on x {
392 enabled: !launcher.dashSwipe
393 StandardAnimation {}
394 }
396 property bool fullyShown: showProgress === 1.0
397 onFullyShownChanged: {
398 // Wait until the greeter is completely covering lockscreen before resetting it.
399 if (fullyShown && !LightDM.Greeter.authenticated) {
400 lockscreen.reset();
401 lockscreen.show();
402 }
403 }
405 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
406 onShowProgressChanged: {
407 if (showProgress === 0) {
408 if (LightDM.Greeter.authenticated) {
409 greeter.login()
410 } else if (greeter.narrowMode) {
411 lockscreen.clear(false) // to reset focus if necessary
412 }
413 }
414 }
416 Greeter {
417 id: greeter
418 objectName: "greeter"
420 signal sessionStarted() // helpful for tests
422 property string fakeActiveForApp: ""
424 available: true
425 hides: [launcher, panel.indicators]
426 shown: true
427 loadContent: required || lockscreen.required // keeps content in memory for quick show()
429 defaultBackground: shell.background
431 width: parent.width
432 height: parent.height
434 dragHandleWidth: shell.edgeSize
436 function login() {
437 enabled = false;
438 if (LightDM.Greeter.startSessionSync()) {
439 sessionStarted();
440 greeter.hide();
441 lockscreen.hide();
442 launcher.hide();
443 }
444 enabled = true;
445 }
447 onShownChanged: {
448 if (shown) {
449 if (greeter.narrowMode) {
450 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
451 }
452 greeter.fakeActiveForApp = "";
453 greeter.forceActiveFocus();
454 }
455 }
457 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
458 Component.onCompleted: {
459 Connectivity.unlockAllModems()
460 } */
462 onUnlocked: greeter.hide()
463 onSelected: {
464 // Update launcher items for new user
465 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
466 AccountsService.user = user;
467 LauncherModel.setUser(user);
468 }
470 onTease: launcher.tease()
472 Binding {
473 target: ApplicationManager
474 property: "suspended"
475 value: greeter.shown && greeterWrapper.showProgress == 1
476 }
477 }
478 }
480 Connections {
481 id: powerConnection
482 target: Powerd
484 onStatusChanged: {
485 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
486 greeter.showNow()
487 }
488 }
489 }
491 function showHome() {
492 if (edgeDemo.running) {
493 return
494 }
496 if (LightDM.Greeter.active) {
497 if (!LightDM.Greeter.authenticated) {
498 lockscreen.show()
499 }
500 greeter.hide()
501 }
503 var animate = !LightDM.Greeter.active && !stages.shown
504 dash.setCurrentScope("clickscope", animate, false)
505 ApplicationManager.requestFocusApplication("unity8-dash")
506 }
508 function showDash() {
509 if (shell.locked) {
510 return;
511 }
512 if (greeter.shown) {
513 greeter.hideRight();
514 launcher.fadeOut();
515 }
517 ApplicationManager.requestFocusApplication("unity8-dash")
518 launcher.fadeOut();
519 }
521 Item {
522 id: overlay
523 z: 10
525 anchors.fill: parent
527 Panel {
528 id: panel
529 objectName: "panel"
530 anchors.fill: parent //because this draws indicator menus
531 indicators {
532 hides: [launcher]
533 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
534 contentEnabled: edgeDemo.panelContentEnabled
535 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
536 panelHeight: units.gu(3)
537 }
539 property bool topmostApplicationIsFullscreen:
540 ApplicationManager.focusedApplicationId &&
541 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
543 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
544 || greeter.fakeActiveForApp !== ""
545 }
547 Launcher {
548 id: launcher
549 objectName: "launcher"
551 readonly property bool dashSwipe: progress > 0
553 anchors.top: parent.top
554 anchors.bottom: parent.bottom
555 width: parent.width
556 dragAreaWidth: shell.edgeSize
557 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
559 onShowDashHome: showHome()
560 onDash: showDash()
561 onDashSwipeChanged: {
562 if (dashSwipe) {
563 dash.setCurrentScope("clickscope", false, true)
564 }
565 }
566 onLauncherApplicationSelected: {
567 if (greeter.fakeActiveForApp !== "") {
568 lockscreen.show()
569 }
570 if (!edgeDemo.running)
571 shell.activateApplication(appId)
572 }
573 onShownChanged: {
574 if (shown) {
575 panel.indicators.hide()
576 }
577 }
578 }
580 Rectangle {
581 id: modalNotificationBackground
583 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
584 color: "#000000"
585 anchors.fill: parent
586 opacity: 0.9
588 MouseArea {
589 anchors.fill: parent
590 }
591 }
593 Notifications {
594 id: notifications
596 model: NotificationBackend.Model
597 margin: units.gu(1)
599 y: panel.panelHeight
600 width: parent.width
601 height: parent.height - panel.panelHeight
603 states: [
604 State {
605 name: "narrow"
606 when: overlay.width <= units.gu(60)
607 AnchorChanges { target: notifications; anchors.left: parent.left }
608 },
609 State {
610 name: "wide"
611 when: overlay.width > units.gu(60)
612 AnchorChanges { target: notifications; anchors.left: undefined }
613 PropertyChanges { target: notifications; width: units.gu(38) }
614 }
615 ]
616 }
617 }
619 Dialogs {
620 id: dialogs
621 anchors.fill: parent
622 z: overlay.z + 10
623 onPowerOffClicked: {
624 shutdownFadeOutRectangle.enabled = true;
625 shutdownFadeOutRectangle.visible = true;
626 shutdownFadeOut.start();
627 }
628 }
630 Label {
631 id: alphaDisclaimerLabel
632 anchors.centerIn: parent
633 visible: ApplicationManager.fake ? ApplicationManager.fake : false
634 z: dialogs.z + 10
635 text: "EARLY ALPHA\nNOT READY FOR USE"
636 color: "lightgrey"
637 opacity: 0.2
638 font.weight: Font.Black
639 horizontalAlignment: Text.AlignHCenter
640 verticalAlignment: Text.AlignVCenter
641 fontSizeMode: Text.Fit
642 rotation: -45
643 scale: Math.min(parent.width, parent.height) / width
644 }
646 EdgeDemo {
647 id: edgeDemo
648 z: alphaDisclaimerLabel.z + 10
649 paused: Powerd.status === Powerd.Off // Saves power
650 greeter: greeter
651 launcher: launcher
652 indicators: panel.indicators
653 stages: stages
654 }
656 Connections {
657 target: SessionBroadcast
658 onShowHome: showHome()
659 }
661 Rectangle {
662 id: shutdownFadeOutRectangle
663 z: edgeDemo.z + 10
664 enabled: false
665 visible: false
666 color: "black"
667 anchors.fill: parent
668 opacity: 0.0
669 NumberAnimation on opacity {
670 id: shutdownFadeOut
671 from: 0.0
672 to: 1.0
673 onStopped: {
674 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
675 DBusUnitySessionService.Shutdown();
676 }
677 }
678 }
679 }