on f29d434 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 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 setLockedApp(app) {
74 if (shell.locked) {
75 greeter.lockedApp = 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 setLockedApp(appId)
146 } else if (greeter.hasLockedApp && greeter.lockedApp !== appId) {
147 lockscreen.show();
148 }
149 greeter.hide();
150 launcher.hide();
151 }
153 onFocusedApplicationIdChanged: {
154 if (greeter.hasLockedApp && greeter.lockedApp !== 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 setLockedApp(appId)
168 }
169 launcher.hide();
170 }
171 }
173 Loader {
174 id: applicationsDisplayLoader
175 anchors.fill: parent
177 // When we have a locked app, we only want to show that one app
178 source: (shell.sideStageEnabled && !greeter.hasLockedApp) ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
180 Binding {
181 target: applicationsDisplayLoader.item
182 property: "objectName"
183 value: "stage"
184 }
185 Binding {
186 target: applicationsDisplayLoader.item
187 property: "dragAreaWidth"
188 value: shell.edgeSize
189 }
190 Binding {
191 target: applicationsDisplayLoader.item
192 property: "maximizedAppTopMargin"
193 // Not just using panel.panelHeight as that changes depending on the focused app.
194 value: panel.indicators.panelHeight
195 }
196 Binding {
197 target: applicationsDisplayLoader.item
198 property: "interactive"
199 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
200 }
201 Binding {
202 target: applicationsDisplayLoader.item
203 property: "spreadEnabled"
204 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp // to support emergency dialer hack
205 }
206 Binding {
207 target: applicationsDisplayLoader.item
208 property: "inverseProgress"
209 value: launcher.progress
210 }
211 }
212 }
214 InputMethod {
215 id: inputMethod
216 objectName: "inputMethod"
217 anchors { fill: parent; topMargin: panel.panelHeight }
218 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
219 }
221 Connections {
222 target: SurfaceManager
223 onSurfaceCreated: {
224 if (surface.type == MirSurfaceItem.InputMethod) {
225 inputMethod.surface = surface;
226 }
227 }
229 onSurfaceDestroyed: {
230 if (inputMethod.surface == surface) {
231 inputMethod.surface = null;
232 surface.parent = null;
233 }
234 if (!surface.parent) {
235 // there's no one displaying it. delete it right away
236 surface.release();
237 }
238 }
239 }
240 Connections {
241 target: SessionManager
242 onSessionStopping: {
243 if (!session.parentSession && !session.application) {
244 // nothing is using it. delete it right away
245 session.release();
246 }
247 }
248 }
250 Lockscreen {
251 id: lockscreen
252 objectName: "lockscreen"
254 readonly property int backgroundTopMargin: -panel.panelHeight
256 hides: [launcher, panel.indicators]
257 shown: false
258 enabled: true
259 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
260 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
261 y: panel.panelHeight
262 visible: required
263 width: parent.width
264 height: parent.height - panel.panelHeight
265 background: shell.background
266 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
267 minPinLength: 4
268 maxPinLength: 4
270 onEntered: LightDM.Greeter.respond(passphrase);
271 onCancel: greeter.show()
272 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter locked-app mode
274 onShownChanged: if (shown) greeter.lockedApp = ""
276 Component.onCompleted: {
277 if (greeter.narrowMode) {
278 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
279 }
280 }
281 }
283 Connections {
284 target: LightDM.Greeter
286 onShowGreeter: greeter.show()
287 onHideGreeter: greeter.login()
289 onShowPrompt: {
290 if (greeter.narrowMode) {
291 if (isDefaultPrompt) {
292 if (lockscreen.alphaNumeric) {
293 lockscreen.infoText = i18n.tr("Enter your passphrase")
294 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
295 } else {
296 lockscreen.infoText = i18n.tr("Enter your passcode")
297 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
298 }
299 } else {
300 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
301 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
302 }
304 lockscreen.show();
305 }
306 }
308 onPromptlessChanged: {
309 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
310 lockscreen.hide()
311 } else {
312 lockscreen.reset();
313 lockscreen.show();
314 }
315 }
317 onAuthenticationComplete: {
318 if (LightDM.Greeter.authenticated) {
319 AccountsService.failedLogins = 0
320 }
321 // Else only penalize user for a failed login if they actually were
322 // prompted for a password. We do this below after the promptless
323 // early exit.
325 if (LightDM.Greeter.promptless) {
326 return;
327 }
329 if (LightDM.Greeter.authenticated) {
330 greeter.login();
331 } else {
332 AccountsService.failedLogins++
333 if (maxFailedLogins >= 2) { // require at least a warning
334 if (AccountsService.failedLogins === maxFailedLogins - 1) {
335 var title = lockscreen.alphaNumeric ?
336 i18n.tr("Sorry, incorrect passphrase.") :
337 i18n.tr("Sorry, incorrect passcode.")
338 var text = i18n.tr("This will be your last attempt.") + " " +
339 (lockscreen.alphaNumeric ?
340 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
341 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
342 lockscreen.showInfoPopup(title, text)
343 } else if (AccountsService.failedLogins >= maxFailedLogins) {
344 SystemImage.factoryReset() // Ouch!
345 }
346 }
347 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
348 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
349 }
351 lockscreen.clear(true);
352 if (greeter.narrowMode) {
353 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
354 }
355 }
356 }
357 }
359 Binding {
360 target: LightDM.Greeter
361 property: "active"
362 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
363 }
365 Rectangle {
366 anchors.fill: parent
367 color: "black"
368 opacity: greeterWrapper.showProgress * 0.8
369 }
371 Item {
372 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
373 id: greeterWrapper
374 x: launcher.progress
375 y: panel.panelHeight
376 width: parent.width
377 height: parent.height - panel.panelHeight
379 Behavior on x {
380 enabled: !launcher.dashSwipe
381 StandardAnimation {}
382 }
384 property bool fullyShown: showProgress === 1.0
385 onFullyShownChanged: {
386 // Wait until the greeter is completely covering lockscreen before resetting it.
387 if (fullyShown && !LightDM.Greeter.authenticated) {
388 lockscreen.reset();
389 lockscreen.show();
390 }
391 }
393 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
394 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
396 Greeter {
397 id: greeter
398 objectName: "greeter"
400 signal sessionStarted() // helpful for tests
402 property string lockedApp: ""
403 property bool hasLockedApp: lockedApp !== ""
405 available: true
406 hides: [launcher, panel.indicators]
407 shown: true
408 loadContent: required || lockscreen.required // keeps content in memory for quick show()
410 defaultBackground: shell.background
412 width: parent.width
413 height: parent.height
415 dragHandleWidth: shell.edgeSize
417 function login() {
418 enabled = false;
419 if (LightDM.Greeter.startSessionSync()) {
420 sessionStarted();
421 greeter.hide();
422 lockscreen.hide();
423 launcher.hide();
424 }
425 enabled = true;
426 }
428 onShownChanged: {
429 if (shown) {
430 if (greeter.narrowMode) {
431 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
432 }
433 greeter.lockedApp = "";
434 greeter.forceActiveFocus();
435 }
436 }
438 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
439 Component.onCompleted: {
440 Connectivity.unlockAllModems()
441 } */
443 onUnlocked: greeter.hide()
444 onSelected: {
445 // Update launcher items for new user
446 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
447 AccountsService.user = user;
448 LauncherModel.setUser(user);
449 }
451 onTease: launcher.tease()
453 Binding {
454 target: ApplicationManager
455 property: "suspended"
456 value: greeter.shown && greeterWrapper.showProgress == 1
457 }
458 }
459 }
461 Connections {
462 id: powerConnection
463 target: Powerd
465 onStatusChanged: {
466 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
467 greeter.showNow()
468 }
469 }
470 }
472 function showHome() {
473 if (edgeDemo.running) {
474 return
475 }
477 if (LightDM.Greeter.active) {
478 if (!LightDM.Greeter.authenticated) {
479 lockscreen.show()
480 }
481 greeter.hide()
482 }
484 var animate = !LightDM.Greeter.active && !stages.shown
485 dash.setCurrentScope("clickscope", animate, false)
486 ApplicationManager.requestFocusApplication("unity8-dash")
487 }
489 function showDash() {
490 if (shell.locked) {
491 return;
492 }
493 if (greeter.shown) {
494 greeter.hideRight();
495 launcher.fadeOut();
496 }
498 ApplicationManager.requestFocusApplication("unity8-dash")
499 launcher.fadeOut();
500 }
502 Item {
503 id: overlay
504 z: 10
506 anchors.fill: parent
508 Panel {
509 id: panel
510 objectName: "panel"
511 anchors.fill: parent //because this draws indicator menus
512 indicators {
513 hides: [launcher]
514 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
515 contentEnabled: edgeDemo.panelContentEnabled
516 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
517 panelHeight: units.gu(3)
518 }
520 property bool topmostApplicationIsFullscreen:
521 ApplicationManager.focusedApplicationId &&
522 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
524 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
525 || greeter.hasLockedApp
526 }
528 Launcher {
529 id: launcher
530 objectName: "launcher"
532 readonly property bool dashSwipe: progress > 0
534 anchors.top: parent.top
535 anchors.bottom: parent.bottom
536 width: parent.width
537 dragAreaWidth: shell.edgeSize
538 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
540 onShowDashHome: showHome()
541 onDash: showDash()
542 onDashSwipeChanged: {
543 if (dashSwipe) {
544 dash.setCurrentScope("clickscope", false, true)
545 }
546 }
547 onLauncherApplicationSelected: {
548 if (greeter.hasLockedApp) {
549 lockscreen.show()
550 }
551 if (!edgeDemo.running)
552 shell.activateApplication(appId)
553 }
554 onShownChanged: {
555 if (shown) {
556 panel.indicators.hide()
557 }
558 }
559 }
561 Rectangle {
562 id: modalNotificationBackground
564 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
565 color: "#000000"
566 anchors.fill: parent
567 opacity: 0.9
569 MouseArea {
570 anchors.fill: parent
571 }
572 }
574 Notifications {
575 id: notifications
577 model: NotificationBackend.Model
578 margin: units.gu(1)
580 y: panel.panelHeight
581 width: parent.width
582 height: parent.height - panel.panelHeight
584 states: [
585 State {
586 name: "narrow"
587 when: overlay.width <= units.gu(60)
588 AnchorChanges { target: notifications; anchors.left: parent.left }
589 },
590 State {
591 name: "wide"
592 when: overlay.width > units.gu(60)
593 AnchorChanges { target: notifications; anchors.left: undefined }
594 PropertyChanges { target: notifications; width: units.gu(38) }
595 }
596 ]
597 }
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 paused: Powerd.status === Powerd.Off // Saves power
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 }