on ee4510d help sign in
unity8/qml/Shell.qml
655 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 setFakeActiveForApp(app) {
74 if (shell.locked) {
75 greeter.fakeActiveForApp = 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 setFakeActiveForApp(appId)
146 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
147 lockscreen.show();
148 }
149 greeter.hide();
150 }
152 onFocusedApplicationIdChanged: {
153 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
154 lockscreen.show();
155 }
156 panel.indicators.hide();
157 }
159 onApplicationAdded: {
160 if (greeter.shown && appId != "unity8-dash") {
161 greeter.hide();
162 }
163 if (appId === "dialer-app") {
164 // Always let the dialer-app through. Either user asked
165 // for an emergency call or accepted an incoming call.
166 setFakeActiveForApp(appId)
167 }
168 }
169 }
171 Loader {
172 id: applicationsDisplayLoader
173 anchors.fill: parent
175 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
177 Binding {
178 target: applicationsDisplayLoader.item
179 property: "objectName"
180 value: "stage"
181 }
182 Binding {
183 target: applicationsDisplayLoader.item
184 property: "dragAreaWidth"
185 value: shell.edgeSize
186 }
187 Binding {
188 target: applicationsDisplayLoader.item
189 property: "maximizedAppTopMargin"
190 // Not just using panel.panelHeight as that changes depending on the focused app.
191 value: panel.indicators.panelHeight
192 }
193 Binding {
194 target: applicationsDisplayLoader.item
195 property: "interactive"
196 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
197 }
198 Binding {
199 target: applicationsDisplayLoader.item
200 property: "spreadEnabled"
201 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
202 }
203 Binding {
204 target: applicationsDisplayLoader.item
205 property: "inverseProgress"
206 value: launcher.progress
207 }
208 }
209 }
211 InputMethod {
212 id: inputMethod
213 objectName: "inputMethod"
214 anchors { fill: parent; topMargin: panel.panelHeight }
215 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
216 }
218 Connections {
219 target: SurfaceManager
220 onSurfaceCreated: {
221 if (surface.type == MirSurfaceItem.InputMethod) {
222 inputMethod.surface = surface;
223 }
224 }
226 onSurfaceDestroyed: {
227 if (inputMethod.surface == surface) {
228 inputMethod.surface = null;
229 surface.parent = null;
230 }
231 if (!surface.parent) {
232 // there's no one displaying it. delete it right away
233 surface.release();
234 }
235 }
236 }
238 Lockscreen {
239 id: lockscreen
240 objectName: "lockscreen"
242 readonly property int backgroundTopMargin: -panel.panelHeight
244 hides: [launcher, panel.indicators]
245 shown: false
246 enabled: true
247 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
248 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
249 y: panel.panelHeight
250 visible: required
251 width: parent.width
252 height: parent.height - panel.panelHeight
253 background: shell.background
254 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
255 minPinLength: 4
256 maxPinLength: 4
258 onEntered: LightDM.Greeter.respond(passphrase);
259 onCancel: greeter.show()
260 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
262 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
264 Component.onCompleted: {
265 if (greeter.narrowMode) {
266 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
267 }
268 }
269 }
271 Connections {
272 target: LightDM.Greeter
274 onShowGreeter: greeter.show()
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 PIN")
284 lockscreen.errorText = i18n.tr("Sorry, incorrect PIN")
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 lockscreen.hide();
318 greeter.login();
319 } else {
320 AccountsService.failedLogins++
321 if (maxFailedLogins >= 2) { // require at least a warning
322 if (AccountsService.failedLogins === maxFailedLogins - 1) {
323 var title = lockscreen.alphaNumeric ?
324 i18n.tr("Sorry, incorrect passphrase.") :
325 i18n.tr("Sorry, incorrect passcode.")
326 var text = i18n.tr("This will be your last attempt.") + " " +
327 (lockscreen.alphaNumeric ?
328 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
329 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
330 lockscreen.showInfoPopup(title, text)
331 } else if (AccountsService.failedLogins >= maxFailedLogins) {
332 SystemImage.factoryReset() // Ouch!
333 }
334 }
335 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
336 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
337 }
339 lockscreen.clear(true);
340 if (greeter.narrowMode) {
341 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
342 }
343 }
344 }
345 }
347 Binding {
348 target: LightDM.Greeter
349 property: "active"
350 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
351 }
353 Rectangle {
354 anchors.fill: parent
355 color: "black"
356 opacity: greeterWrapper.showProgress * 0.8
357 }
359 Item {
360 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
361 id: greeterWrapper
362 x: launcher.progress
363 y: panel.panelHeight
364 width: parent.width
365 height: parent.height - panel.panelHeight
367 Behavior on x {
368 enabled: !launcher.dashSwipe
369 StandardAnimation {}
370 }
372 property bool fullyShown: showProgress === 1.0
373 onFullyShownChanged: {
374 // Wait until the greeter is completely covering lockscreen before resetting it.
375 if (fullyShown && !LightDM.Greeter.authenticated) {
376 lockscreen.reset();
377 lockscreen.show();
378 }
379 }
381 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
382 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
384 Greeter {
385 id: greeter
386 objectName: "greeter"
388 signal sessionStarted() // helpful for tests
390 property string fakeActiveForApp: ""
392 available: true
393 hides: [launcher, panel.indicators]
394 shown: true
395 loadContent: required || lockscreen.required // keeps content in memory for quick show()
397 defaultBackground: shell.background
399 width: parent.width
400 height: parent.height
402 dragHandleWidth: shell.edgeSize
404 function login() {
405 enabled = false;
406 if (LightDM.Greeter.startSessionSync()) {
407 sessionStarted();
408 greeter.hide();
409 lockscreen.hide();
410 launcher.hide();
411 }
412 enabled = true;
413 }
415 onShownChanged: {
416 if (shown) {
417 if (greeter.narrowMode) {
418 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
419 }
420 greeter.fakeActiveForApp = "";
421 greeter.forceActiveFocus();
422 }
423 }
425 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
426 Component.onCompleted: {
427 Connectivity.unlockAllModems()
428 } */
430 onUnlocked: greeter.hide()
431 onSelected: {
432 // Update launcher items for new user
433 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
434 AccountsService.user = user;
435 LauncherModel.setUser(user);
436 }
438 onTease: launcher.tease()
440 Binding {
441 target: ApplicationManager
442 property: "suspended"
443 value: greeter.shown && greeterWrapper.showProgress == 1
444 }
445 }
446 }
448 Connections {
449 id: powerConnection
450 target: Powerd
452 onStatusChanged: {
453 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
454 greeter.showNow()
455 }
456 }
457 }
459 function showHome() {
460 if (edgeDemo.running) {
461 return
462 }
464 if (LightDM.Greeter.active) {
465 if (!LightDM.Greeter.authenticated) {
466 lockscreen.show()
467 }
468 greeter.hide()
469 }
471 var animate = !LightDM.Greeter.active && !stages.shown
472 dash.setCurrentScope("clickscope", animate, false)
473 ApplicationManager.requestFocusApplication("unity8-dash")
474 }
476 function showDash() {
477 if (shell.locked) {
478 return;
479 }
480 if (greeter.shown) {
481 greeter.hideRight();
482 launcher.fadeOut();
483 }
485 ApplicationManager.requestFocusApplication("unity8-dash")
486 launcher.fadeOut();
487 }
489 Item {
490 id: overlay
491 z: 10
493 anchors.fill: parent
495 Panel {
496 id: panel
497 objectName: "panel"
498 anchors.fill: parent //because this draws indicator menus
499 indicators {
500 hides: [launcher]
501 available: edgeDemo.panelEnabled && !shell.locked
502 contentEnabled: edgeDemo.panelContentEnabled
503 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
504 panelHeight: units.gu(3)
505 }
507 property bool topmostApplicationIsFullscreen:
508 ApplicationManager.focusedApplicationId &&
509 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
511 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
512 || greeter.fakeActiveForApp !== ""
513 }
515 Launcher {
516 id: launcher
517 objectName: "launcher"
519 readonly property bool dashSwipe: progress > 0
521 anchors.top: parent.top
522 anchors.bottom: parent.bottom
523 width: parent.width
524 dragAreaWidth: shell.edgeSize
525 available: edgeDemo.launcherEnabled && !shell.locked
527 onShowDashHome: showHome()
528 onDash: showDash()
529 onDashSwipeChanged: {
530 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
531 dash.setCurrentScope("clickscope", false, true)
532 }
533 }
534 onLauncherApplicationSelected: {
535 if (greeter.fakeActiveForApp !== "") {
536 lockscreen.show()
537 }
538 if (!edgeDemo.running)
539 shell.activateApplication(appId)
540 }
541 onShownChanged: {
542 if (shown) {
543 panel.indicators.hide()
544 }
545 }
546 }
548 Rectangle {
549 id: modalNotificationBackground
551 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
552 color: "#000000"
553 anchors.fill: parent
554 opacity: 0.5
556 MouseArea {
557 anchors.fill: parent
558 }
559 }
561 Notifications {
562 id: notifications
564 model: NotificationBackend.Model
565 margin: units.gu(1)
567 y: panel.panelHeight
568 width: parent.width
569 height: parent.height - panel.panelHeight
571 states: [
572 State {
573 name: "narrow"
574 when: overlay.width <= units.gu(60)
575 AnchorChanges { target: notifications; anchors.left: parent.left }
576 },
577 State {
578 name: "wide"
579 when: overlay.width > units.gu(60)
580 AnchorChanges { target: notifications; anchors.left: undefined }
581 PropertyChanges { target: notifications; width: units.gu(38) }
582 }
583 ]
584 }
585 }
587 Binding {
588 target: i18n
589 property: "domain"
590 value: "unity8"
591 }
593 Dialogs {
594 id: dialogs
595 anchors.fill: parent
596 z: overlay.z + 10
597 onPowerOffClicked: {
598 shutdownFadeOutRectangle.enabled = true;
599 shutdownFadeOutRectangle.visible = true;
600 shutdownFadeOut.start();
601 }
602 }
604 Label {
605 id: alphaDisclaimerLabel
606 anchors.centerIn: parent
607 visible: ApplicationManager.fake ? ApplicationManager.fake : false
608 z: dialogs.z + 10
609 text: "EARLY ALPHA\nNOT READY FOR USE"
610 color: "lightgrey"
611 opacity: 0.2
612 font.weight: Font.Black
613 horizontalAlignment: Text.AlignHCenter
614 verticalAlignment: Text.AlignVCenter
615 fontSizeMode: Text.Fit
616 rotation: -45
617 scale: Math.min(parent.width, parent.height) / width
618 }
620 EdgeDemo {
621 id: edgeDemo
622 z: alphaDisclaimerLabel.z + 10
623 paused: Powerd.status === Powerd.Off // Saves power
624 greeter: greeter
625 launcher: launcher
626 indicators: panel.indicators
627 stages: stages
628 }
630 Connections {
631 target: SessionBroadcast
632 onShowHome: showHome()
633 }
635 Rectangle {
636 id: shutdownFadeOutRectangle
637 z: edgeDemo.z + 10
638 enabled: false
639 visible: false
640 color: "black"
641 anchors.fill: parent
642 opacity: 0.0
643 NumberAnimation on opacity {
644 id: shutdownFadeOut
645 from: 0.0
646 to: 1.0
647 onStopped: {
648 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
649 DBusUnitySessionService.Shutdown();
650 }
651 }
652 }
653 }