on b2ad2f5 help sign in
unity8/qml/Shell.qml
660 lines 22 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 failedLoginsDelaySeconds: 5 * 60 // seconds 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 readonly property int backgroundTopMargin: -panel.panelHeight
255 hides: [launcher, panel.indicators]
256 shown: false
257 enabled: true
258 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
259 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
260 y: panel.panelHeight
261 visible: required
262 width: parent.width
263 height: parent.height - panel.panelHeight
264 background: shell.background
265 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
266 minPinLength: 4
267 maxPinLength: 4
269 onEntered: LightDM.Greeter.respond(passphrase);
270 onCancel: greeter.show()
271 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
273 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
275 Component.onCompleted: {
276 if (greeter.narrowMode) {
277 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
278 }
279 }
280 }
282 Connections {
283 target: LightDM.Greeter
285 onShowGreeter: greeter.show()
286 onHideGreeter: greeter.login()
288 onShowPrompt: {
289 if (greeter.narrowMode) {
290 if (isDefaultPrompt) {
291 if (lockscreen.alphaNumeric) {
292 lockscreen.infoText = i18n.tr("Enter your passphrase")
293 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
294 } else {
295 lockscreen.infoText = i18n.tr("Enter your passcode")
296 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
297 }
298 } else {
299 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
300 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
301 }
303 lockscreen.show();
304 }
305 }
307 onPromptlessChanged: {
308 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
309 lockscreen.hide()
310 } else {
311 lockscreen.reset();
312 lockscreen.show();
313 }
314 }
316 onAuthenticationComplete: {
317 if (LightDM.Greeter.authenticated) {
318 AccountsService.failedLogins = 0
319 }
320 // Else only penalize user for a failed login if they actually were
321 // prompted for a password. We do this below after the promptless
322 // early exit.
324 if (LightDM.Greeter.promptless) {
325 return;
326 }
328 if (LightDM.Greeter.authenticated) {
329 greeter.login();
330 } else {
331 AccountsService.failedLogins++
332 if (maxFailedLogins >= 2) { // require at least a warning
333 if (AccountsService.failedLogins === maxFailedLogins - 1) {
334 var title = lockscreen.alphaNumeric ?
335 i18n.tr("Sorry, incorrect passphrase.") :
336 i18n.tr("Sorry, incorrect passcode.")
337 var text = i18n.tr("This will be your last attempt.") + " " +
338 (lockscreen.alphaNumeric ?
339 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
340 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
341 lockscreen.showInfoPopup(title, text)
342 } else if (AccountsService.failedLogins >= maxFailedLogins) {
343 SystemImage.factoryReset() // Ouch!
344 }
345 }
346 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
347 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
348 }
350 lockscreen.clear(true);
351 if (greeter.narrowMode) {
352 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
353 }
354 }
355 }
356 }
358 Binding {
359 target: LightDM.Greeter
360 property: "active"
361 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
362 }
364 Rectangle {
365 anchors.fill: parent
366 color: "black"
367 opacity: greeterWrapper.showProgress * 0.8
368 }
370 Item {
371 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
372 id: greeterWrapper
373 x: launcher.progress
374 y: panel.panelHeight
375 width: parent.width
376 height: parent.height - panel.panelHeight
378 Behavior on x {
379 enabled: !launcher.dashSwipe
380 StandardAnimation {}
381 }
383 property bool fullyShown: showProgress === 1.0
384 onFullyShownChanged: {
385 // Wait until the greeter is completely covering lockscreen before resetting it.
386 if (fullyShown && !LightDM.Greeter.authenticated) {
387 lockscreen.reset();
388 lockscreen.show();
389 }
390 }
392 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
393 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
395 Greeter {
396 id: greeter
397 objectName: "greeter"
399 signal sessionStarted() // helpful for tests
401 property string fakeActiveForApp: ""
403 available: true
404 hides: [launcher, panel.indicators]
405 shown: true
406 loadContent: required || lockscreen.required // keeps content in memory for quick show()
408 defaultBackground: shell.background
410 width: parent.width
411 height: parent.height
413 dragHandleWidth: shell.edgeSize
415 function login() {
416 enabled = false;
417 if (LightDM.Greeter.startSessionSync()) {
418 sessionStarted();
419 greeter.hide();
420 lockscreen.hide();
421 launcher.hide();
422 }
423 enabled = true;
424 }
426 onShownChanged: {
427 if (shown) {
428 if (greeter.narrowMode) {
429 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
430 }
431 greeter.fakeActiveForApp = "";
432 greeter.forceActiveFocus();
433 }
434 }
436 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
437 Component.onCompleted: {
438 Connectivity.unlockAllModems()
439 } */
441 onUnlocked: greeter.hide()
442 onSelected: {
443 // Update launcher items for new user
444 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
445 AccountsService.user = user;
446 LauncherModel.setUser(user);
447 }
449 onTease: launcher.tease()
451 Binding {
452 target: ApplicationManager
453 property: "suspended"
454 value: greeter.shown && greeterWrapper.showProgress == 1
455 }
456 }
457 }
459 Connections {
460 id: powerConnection
461 target: Powerd
463 onStatusChanged: {
464 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
465 greeter.showNow()
466 }
467 }
468 }
470 function showHome() {
471 if (edgeDemo.running) {
472 return
473 }
475 if (LightDM.Greeter.active) {
476 if (!LightDM.Greeter.authenticated) {
477 lockscreen.show()
478 }
479 greeter.hide()
480 }
482 var animate = !LightDM.Greeter.active && !stages.shown
483 dash.setCurrentScope("clickscope", animate, false)
484 ApplicationManager.requestFocusApplication("unity8-dash")
485 }
487 function showDash() {
488 if (shell.locked) {
489 return;
490 }
491 if (greeter.shown) {
492 greeter.hideRight();
493 launcher.fadeOut();
494 }
496 ApplicationManager.requestFocusApplication("unity8-dash")
497 launcher.fadeOut();
498 }
500 Item {
501 id: overlay
502 z: 10
504 anchors.fill: parent
506 Panel {
507 id: panel
508 objectName: "panel"
509 anchors.fill: parent //because this draws indicator menus
510 indicators {
511 hides: [launcher]
512 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
513 contentEnabled: edgeDemo.panelContentEnabled
514 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
515 panelHeight: units.gu(3)
516 }
518 property bool topmostApplicationIsFullscreen:
519 ApplicationManager.focusedApplicationId &&
520 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
522 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
523 || greeter.fakeActiveForApp !== ""
524 }
526 Launcher {
527 id: launcher
528 objectName: "launcher"
530 readonly property bool dashSwipe: progress > 0
532 anchors.top: parent.top
533 anchors.bottom: parent.bottom
534 width: parent.width
535 dragAreaWidth: shell.edgeSize
536 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
538 onShowDashHome: showHome()
539 onDash: showDash()
540 onDashSwipeChanged: {
541 if (dashSwipe) {
542 dash.setCurrentScope("clickscope", false, true)
543 }
544 }
545 onLauncherApplicationSelected: {
546 if (greeter.fakeActiveForApp !== "") {
547 lockscreen.show()
548 }
549 if (!edgeDemo.running)
550 shell.activateApplication(appId)
551 }
552 onShownChanged: {
553 if (shown) {
554 panel.indicators.hide()
555 }
556 }
557 }
559 Rectangle {
560 id: modalNotificationBackground
562 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
563 color: "#000000"
564 anchors.fill: parent
565 opacity: 0.9
567 MouseArea {
568 anchors.fill: parent
569 }
570 }
572 Notifications {
573 id: notifications
575 model: NotificationBackend.Model
576 margin: units.gu(1)
578 y: panel.panelHeight + margin
579 width: parent.width
580 height: parent.height - panel.panelHeight - margin
582 states: [
583 State {
584 name: "narrow"
585 when: overlay.width <= units.gu(60)
586 AnchorChanges { target: notifications; anchors.left: parent.left }
587 },
588 State {
589 name: "wide"
590 when: overlay.width > units.gu(60)
591 AnchorChanges { target: notifications; anchors.left: undefined }
592 PropertyChanges { target: notifications; width: units.gu(38) }
593 }
594 ]
595 }
596 }
598 Dialogs {
599 id: dialogs
600 anchors.fill: parent
601 z: overlay.z + 10
602 onPowerOffClicked: {
603 shutdownFadeOutRectangle.enabled = true;
604 shutdownFadeOutRectangle.visible = true;
605 shutdownFadeOut.start();
606 }
607 }
609 Label {
610 id: alphaDisclaimerLabel
611 anchors.centerIn: parent
612 visible: ApplicationManager.fake ? ApplicationManager.fake : false
613 z: dialogs.z + 10
614 text: "EARLY ALPHA\nNOT READY FOR USE"
615 color: "lightgrey"
616 opacity: 0.2
617 font.weight: Font.Black
618 horizontalAlignment: Text.AlignHCenter
619 verticalAlignment: Text.AlignVCenter
620 fontSizeMode: Text.Fit
621 rotation: -45
622 scale: Math.min(parent.width, parent.height) / width
623 }
625 EdgeDemo {
626 id: edgeDemo
627 z: alphaDisclaimerLabel.z + 10
628 paused: Powerd.status === Powerd.Off // Saves power
629 greeter: greeter
630 launcher: launcher
631 indicators: panel.indicators
632 stages: stages
633 }
635 Connections {
636 target: SessionBroadcast
637 onShowHome: showHome()
638 }
640 Rectangle {
641 id: shutdownFadeOutRectangle
642 z: edgeDemo.z + 10
643 enabled: false
644 visible: false
645 color: "black"
646 anchors.fill: parent
647 opacity: 0.0
648 NumberAnimation on opacity {
649 id: shutdownFadeOut
650 from: 0.0
651 to: 1.0
652 onStopped: {
653 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
654 DBusUnitySessionService.Shutdown();
655 }
656 }
657 }
658 }