on 361ed57 help sign in
unity8/qml/Shell.qml
662 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 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 }
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()
274 onHideGreeter: greeter.login()
276 onShowPrompt: {
277 if (greeter.narrowMode) {
278 if (isDefaultPrompt) {
279 if (lockscreen.alphaNumeric) {
280 lockscreen.infoText = i18n.tr("Enter your passphrase")
281 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
282 } else {
283 lockscreen.infoText = i18n.tr("Enter your passcode")
284 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
285 }
286 } else {
287 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
288 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
289 }
291 lockscreen.show();
292 }
293 }
295 onPromptlessChanged: {
296 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
297 lockscreen.hide()
298 } else {
299 lockscreen.reset();
300 lockscreen.show();
301 }
302 }
304 onAuthenticationComplete: {
305 if (LightDM.Greeter.authenticated) {
306 AccountsService.failedLogins = 0
307 }
308 // Else only penalize user for a failed login if they actually were
309 // prompted for a password. We do this below after the promptless
310 // early exit.
312 if (LightDM.Greeter.promptless) {
313 return;
314 }
316 if (LightDM.Greeter.authenticated) {
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 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
425 Component.onCompleted: {
426 Connectivity.unlockAllModems()
427 } */
429 onUnlocked: greeter.hide()
430 onSelected: {
431 // Update launcher items for new user
432 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
433 AccountsService.user = user;
434 LauncherModel.setUser(user);
435 }
437 onTease: launcher.tease()
439 Binding {
440 target: ApplicationManager
441 property: "suspended"
442 value: greeter.shown && greeterWrapper.showProgress == 1
443 }
444 }
445 }
447 Connections {
448 id: powerConnection
449 target: Powerd
451 onDisplayPowerStateChange: {
452 // We ignore any display-off signals when the proximity sensor
453 // is active. This usually indicates something like a phone call.
454 if (status == Powerd.Off && reason != Powerd.Proximity && !edgeDemo.running) {
455 greeter.showNow();
456 }
458 // No reason to chew demo CPU when user isn't watching
459 if (status == Powerd.Off) {
460 edgeDemo.paused = true;
461 } else if (status == Powerd.On) {
462 edgeDemo.paused = false;
463 }
464 }
465 }
467 function showHome() {
468 if (edgeDemo.running) {
469 return
470 }
472 if (LightDM.Greeter.active) {
473 if (!LightDM.Greeter.authenticated) {
474 lockscreen.show()
475 }
476 greeter.hide()
477 }
479 var animate = !LightDM.Greeter.active && !stages.shown
480 dash.setCurrentScope("clickscope", animate, false)
481 ApplicationManager.requestFocusApplication("unity8-dash")
482 }
484 function showDash() {
485 if (shell.locked) {
486 return;
487 }
488 if (greeter.shown) {
489 greeter.hideRight();
490 launcher.fadeOut();
491 }
493 ApplicationManager.requestFocusApplication("unity8-dash")
494 launcher.fadeOut();
495 }
497 Item {
498 id: overlay
499 z: 10
501 anchors.fill: parent
503 Panel {
504 id: panel
505 objectName: "panel"
506 anchors.fill: parent //because this draws indicator menus
507 indicators {
508 hides: [launcher]
509 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
510 contentEnabled: edgeDemo.panelContentEnabled
511 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
512 panelHeight: units.gu(3)
513 }
515 property bool topmostApplicationIsFullscreen:
516 ApplicationManager.focusedApplicationId &&
517 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
519 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
520 || greeter.fakeActiveForApp !== ""
521 }
523 Launcher {
524 id: launcher
525 objectName: "launcher"
527 readonly property bool dashSwipe: progress > 0
529 anchors.top: parent.top
530 anchors.bottom: parent.bottom
531 width: parent.width
532 dragAreaWidth: shell.edgeSize
533 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
535 onShowDashHome: showHome()
536 onDash: showDash()
537 onDashSwipeChanged: {
538 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
539 dash.setCurrentScope("clickscope", false, true)
540 }
541 }
542 onLauncherApplicationSelected: {
543 if (greeter.fakeActiveForApp !== "") {
544 lockscreen.show()
545 }
546 if (!edgeDemo.running)
547 shell.activateApplication(appId)
548 }
549 onShownChanged: {
550 if (shown) {
551 panel.indicators.hide()
552 }
553 }
554 }
556 Rectangle {
557 id: modalNotificationBackground
559 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
560 color: "#000000"
561 anchors.fill: parent
562 opacity: 0.5
564 MouseArea {
565 anchors.fill: parent
566 }
567 }
569 Notifications {
570 id: notifications
572 model: NotificationBackend.Model
573 margin: units.gu(1)
575 y: panel.panelHeight
576 width: parent.width
577 height: parent.height - panel.panelHeight
579 states: [
580 State {
581 name: "narrow"
582 when: overlay.width <= units.gu(60)
583 AnchorChanges { target: notifications; anchors.left: parent.left }
584 },
585 State {
586 name: "wide"
587 when: overlay.width > units.gu(60)
588 AnchorChanges { target: notifications; anchors.left: undefined }
589 PropertyChanges { target: notifications; width: units.gu(38) }
590 }
591 ]
592 }
593 }
595 Binding {
596 target: i18n
597 property: "domain"
598 value: "unity8"
599 }
601 Dialogs {
602 id: dialogs
603 anchors.fill: parent
604 z: overlay.z + 10
605 onPowerOffClicked: {
606 shutdownFadeOutRectangle.enabled = true;
607 shutdownFadeOutRectangle.visible = true;
608 shutdownFadeOut.start();
609 }
610 }
612 Label {
613 id: alphaDisclaimerLabel
614 anchors.centerIn: parent
615 visible: ApplicationManager.fake ? ApplicationManager.fake : false
616 z: dialogs.z + 10
617 text: "EARLY ALPHA\nNOT READY FOR USE"
618 color: "lightgrey"
619 opacity: 0.2
620 font.weight: Font.Black
621 horizontalAlignment: Text.AlignHCenter
622 verticalAlignment: Text.AlignVCenter
623 fontSizeMode: Text.Fit
624 rotation: -45
625 scale: Math.min(parent.width, parent.height) / width
626 }
628 EdgeDemo {
629 id: edgeDemo
630 z: alphaDisclaimerLabel.z + 10
631 greeter: greeter
632 launcher: launcher
633 indicators: panel.indicators
634 stages: stages
635 }
637 Connections {
638 target: SessionBroadcast
639 onShowHome: showHome()
640 }
642 Rectangle {
643 id: shutdownFadeOutRectangle
644 z: edgeDemo.z + 10
645 enabled: false
646 visible: false
647 color: "black"
648 anchors.fill: parent
649 opacity: 0.0
650 NumberAnimation on opacity {
651 id: shutdownFadeOut
652 from: 0.0
653 to: 1.0
654 onStopped: {
655 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
656 DBusUnitySessionService.Shutdown();
657 }
658 }
659 }
660 }