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