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