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