on fea8816 help sign in
unity8/qml/Shell.qml
661 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.Connectivity 0.1
24import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 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 }
237 Lockscreen {
238 id: lockscreen
239 objectName: "lockscreen"
241 readonly property int backgroundTopMargin: -panel.panelHeight
243 hides: [launcher, panel.indicators]
244 shown: false
245 enabled: true
246 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
247 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
248 y: panel.panelHeight
249 visible: required
250 width: parent.width
251 height: parent.height - panel.panelHeight
252 background: shell.background
253 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
254 minPinLength: 4
255 maxPinLength: 4
257 onEntered: LightDM.Greeter.respond(passphrase);
258 onCancel: greeter.show()
259 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
261 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
263 Component.onCompleted: {
264 if (greeter.narrowMode) {
265 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
266 }
267 }
268 }
270 Connections {
271 target: LightDM.Greeter
273 onShowGreeter: greeter.show()
275 onShowPrompt: {
276 if (greeter.narrowMode) {
277 if (isDefaultPrompt) {
278 if (lockscreen.alphaNumeric) {
279 lockscreen.infoText = i18n.tr("Enter your passphrase")
280 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
281 } else {
282 lockscreen.infoText = i18n.tr("Enter your PIN")
283 lockscreen.errorText = i18n.tr("Sorry, incorrect PIN")
284 }
285 } else {
286 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
287 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
288 }
290 lockscreen.show();
291 }
292 }
294 onPromptlessChanged: {
295 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
296 lockscreen.hide()
297 } else {
298 lockscreen.reset();
299 lockscreen.show();
300 }
301 }
303 onAuthenticationComplete: {
304 if (LightDM.Greeter.authenticated) {
305 AccountsService.failedLogins = 0
306 }
307 // Else only penalize user for a failed login if they actually were
308 // prompted for a password. We do this below after the promptless
309 // early exit.
311 if (LightDM.Greeter.promptless) {
312 return;
313 }
315 if (LightDM.Greeter.authenticated) {
316 lockscreen.hide();
317 greeter.login();
318 } else {
319 AccountsService.failedLogins++
320 if (maxFailedLogins >= 2) { // require at least a warning
321 if (AccountsService.failedLogins === maxFailedLogins - 1) {
322 var title = lockscreen.alphaNumeric ?
323 i18n.tr("Sorry, incorrect passphrase.") :
324 i18n.tr("Sorry, incorrect passcode.")
325 var text = i18n.tr("This will be your last attempt.") + " " +
326 (lockscreen.alphaNumeric ?
327 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
328 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
329 lockscreen.showInfoPopup(title, text)
330 } else if (AccountsService.failedLogins >= maxFailedLogins) {
331 SystemImage.factoryReset() // Ouch!
332 }
333 }
334 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
335 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
336 }
338 lockscreen.clear(true);
339 if (greeter.narrowMode) {
340 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
341 }
342 }
343 }
344 }
346 Binding {
347 target: LightDM.Greeter
348 property: "active"
349 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
350 }
352 Rectangle {
353 anchors.fill: parent
354 color: "black"
355 opacity: greeterWrapper.showProgress * 0.8
356 }
358 Item {
359 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
360 id: greeterWrapper
361 x: launcher.progress
362 y: panel.panelHeight
363 width: parent.width
364 height: parent.height - panel.panelHeight
366 Behavior on x {
367 enabled: !launcher.dashSwipe
368 StandardAnimation {}
369 }
371 property bool fullyShown: showProgress === 1.0
372 onFullyShownChanged: {
373 // Wait until the greeter is completely covering lockscreen before resetting it.
374 if (fullyShown && !LightDM.Greeter.authenticated) {
375 lockscreen.reset();
376 lockscreen.show();
377 }
378 }
380 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
381 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
383 Greeter {
384 id: greeter
385 objectName: "greeter"
387 signal sessionStarted() // helpful for tests
389 property string fakeActiveForApp: ""
391 available: true
392 hides: [launcher, panel.indicators]
393 shown: true
394 loadContent: required || lockscreen.required // keeps content in memory for quick show()
396 defaultBackground: shell.background
398 width: parent.width
399 height: parent.height
401 dragHandleWidth: shell.edgeSize
403 function login() {
404 enabled = false;
405 if (LightDM.Greeter.startSessionSync()) {
406 sessionStarted();
407 greeter.hide();
408 lockscreen.hide();
409 launcher.hide();
410 }
411 enabled = true;
412 }
414 onShownChanged: {
415 if (shown) {
416 if (greeter.narrowMode) {
417 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
418 }
419 greeter.fakeActiveForApp = "";
420 greeter.forceActiveFocus();
421 }
422 }
424 Component.onCompleted: {
425 Connectivity.unlockAllModems()
426 }
428 onUnlocked: greeter.hide()
429 onSelected: {
430 // Update launcher items for new user
431 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
432 AccountsService.user = user;
433 LauncherModel.setUser(user);
434 }
436 onTease: launcher.tease()
438 Binding {
439 target: ApplicationManager
440 property: "suspended"
441 value: greeter.shown && greeterWrapper.showProgress == 1
442 }
443 }
444 }
446 Connections {
447 id: powerConnection
448 target: Powerd
450 onDisplayPowerStateChange: {
451 // We ignore any display-off signals when the proximity sensor
452 // is active. This usually indicates something like a phone call.
453 if (status == Powerd.Off && reason != Powerd.Proximity && !edgeDemo.running) {
454 greeter.showNow();
455 }
457 // No reason to chew demo CPU when user isn't watching
458 if (status == Powerd.Off) {
459 edgeDemo.paused = true;
460 } else if (status == Powerd.On) {
461 edgeDemo.paused = false;
462 }
463 }
464 }
466 function showHome() {
467 if (edgeDemo.running) {
468 return
469 }
471 if (LightDM.Greeter.active) {
472 if (!LightDM.Greeter.authenticated) {
473 lockscreen.show()
474 }
475 greeter.hide()
476 }
478 var animate = !LightDM.Greeter.active && !stages.shown
479 dash.setCurrentScope("clickscope", animate, false)
480 ApplicationManager.requestFocusApplication("unity8-dash")
481 }
483 function showDash() {
484 if (shell.locked) {
485 return;
486 }
487 if (greeter.shown) {
488 greeter.hideRight();
489 launcher.fadeOut();
490 }
492 ApplicationManager.requestFocusApplication("unity8-dash")
493 launcher.fadeOut();
494 }
496 Item {
497 id: overlay
498 z: 10
500 anchors.fill: parent
502 Panel {
503 id: panel
504 objectName: "panel"
505 anchors.fill: parent //because this draws indicator menus
506 indicators {
507 hides: [launcher]
508 available: edgeDemo.panelEnabled && !shell.locked
509 contentEnabled: edgeDemo.panelContentEnabled
510 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
511 panelHeight: units.gu(3)
512 }
514 property bool topmostApplicationIsFullscreen:
515 ApplicationManager.focusedApplicationId &&
516 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
518 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
519 || greeter.fakeActiveForApp !== ""
520 }
522 Launcher {
523 id: launcher
524 objectName: "launcher"
526 readonly property bool dashSwipe: progress > 0
528 anchors.top: parent.top
529 anchors.bottom: parent.bottom
530 width: parent.width
531 dragAreaWidth: shell.edgeSize
532 available: edgeDemo.launcherEnabled && !shell.locked
534 onShowDashHome: showHome()
535 onDash: showDash()
536 onDashSwipeChanged: {
537 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
538 dash.setCurrentScope("clickscope", false, true)
539 }
540 }
541 onLauncherApplicationSelected: {
542 if (greeter.fakeActiveForApp !== "") {
543 lockscreen.show()
544 }
545 if (!edgeDemo.running)
546 shell.activateApplication(appId)
547 }
548 onShownChanged: {
549 if (shown) {
550 panel.indicators.hide()
551 }
552 }
553 }
555 Rectangle {
556 id: modalNotificationBackground
558 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
559 color: "#000000"
560 anchors.fill: parent
561 opacity: 0.5
563 MouseArea {
564 anchors.fill: parent
565 }
566 }
568 Notifications {
569 id: notifications
571 model: NotificationBackend.Model
572 margin: units.gu(1)
574 y: panel.panelHeight
575 width: parent.width
576 height: parent.height - panel.panelHeight
578 states: [
579 State {
580 name: "narrow"
581 when: overlay.width <= units.gu(60)
582 AnchorChanges { target: notifications; anchors.left: parent.left }
583 },
584 State {
585 name: "wide"
586 when: overlay.width > units.gu(60)
587 AnchorChanges { target: notifications; anchors.left: undefined }
588 PropertyChanges { target: notifications; width: units.gu(38) }
589 }
590 ]
591 }
592 }
594 Binding {
595 target: i18n
596 property: "domain"
597 value: "unity8"
598 }
600 Dialogs {
601 id: dialogs
602 anchors.fill: parent
603 z: overlay.z + 10
604 onPowerOffClicked: {
605 shutdownFadeOutRectangle.enabled = true;
606 shutdownFadeOutRectangle.visible = true;
607 shutdownFadeOut.start();
608 }
609 }
611 Label {
612 id: alphaDisclaimerLabel
613 anchors.centerIn: parent
614 visible: ApplicationManager.fake ? ApplicationManager.fake : false
615 z: dialogs.z + 10
616 text: "EARLY ALPHA\nNOT READY FOR USE"
617 color: "lightgrey"
618 opacity: 0.2
619 font.weight: Font.Black
620 horizontalAlignment: Text.AlignHCenter
621 verticalAlignment: Text.AlignVCenter
622 fontSizeMode: Text.Fit
623 rotation: -45
624 scale: Math.min(parent.width, parent.height) / width
625 }
627 EdgeDemo {
628 id: edgeDemo
629 z: alphaDisclaimerLabel.z + 10
630 greeter: greeter
631 launcher: launcher
632 indicators: panel.indicators
633 stages: stages
634 }
636 Connections {
637 target: SessionBroadcast
638 onShowHome: showHome()
639 }
641 Rectangle {
642 id: shutdownFadeOutRectangle
643 z: edgeDemo.z + 10
644 enabled: false
645 visible: false
646 color: "black"
647 anchors.fill: parent
648 opacity: 0.0
649 NumberAnimation on opacity {
650 id: shutdownFadeOut
651 from: 0.0
652 to: 1.0
653 onStopped: {
654 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
655 DBusUnitySessionService.Shutdown();
656 }
657 }
658 }
659 }