on f55a0dc help sign in
unity8/qml/Shell.qml
703 lines 24 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 QtQuick.Window 2.0
19import AccountsService 0.1
20import GSettings 1.0
21import Unity.Application 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
29import Utils 0.1
30import LightDM 0.1 as LightDM
31import Powerd 0.1
32import SessionBroadcast 0.1
33import "Greeter"
34import "Launcher"
35import "Panel"
36import "Components"
37import "Notifications"
38import "Stages"
39import Unity.Notifications 1.0 as NotificationBackend
40import Unity.Session 0.1
41import Unity.DashCommunicator 0.1
43Item {
44 id: shell
46 // this is only here to select the width / height of the window if not running fullscreen
47 property bool tablet: false
48 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
49 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
51 property real edgeSize: units.gu(2)
52 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
53 property url background
54 readonly property real panelHeight: panel.panelHeight
56 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
58 property bool sideStageEnabled: shell.width >= units.gu(100)
59 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
61 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
62 property int failedLoginsDelayAttempts: 7 // number of failed logins
63 property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
65 property int orientation
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67 onDeviceOrientationAngleChanged: {
68 if (!OrientationLock.enabled) {
69 orientation = Screen.orientation;
70 }
71 }
72 readonly property bool orientationLockEnabled: OrientationLock.enabled
73 onOrientationLockEnabledChanged: {
74 if (orientationLockEnabled) {
75 OrientationLock.savedOrientation = Screen.orientation;
76 } else {
77 orientation = Screen.orientation;
78 }
79 }
81 function activateApplication(appId) {
82 if (ApplicationManager.findApplication(appId)) {
83 ApplicationManager.requestFocusApplication(appId);
84 } else {
85 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
86 ApplicationManager.startApplication(appId, execFlags);
87 }
88 }
90 function setFakeActiveForApp(app) {
91 if (shell.locked) {
92 greeter.fakeActiveForApp = app
93 lockscreen.hide()
94 }
95 }
97 Binding {
98 target: LauncherModel
99 property: "applicationManager"
100 value: ApplicationManager
101 }
103 Component.onCompleted: {
104 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
105 if (ApplicationManager.count > 0) {
106 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
107 }
108 if (orientationLockEnabled) {
109 orientation = OrientationLock.savedOrientation;
110 }
111 }
113 GSettings {
114 id: backgroundSettings
115 schema.id: "org.gnome.desktop.background"
116 }
117 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
118 onGSettingsPictureChanged: {
119 shell.background = gSettingsPicture
120 }
122 VolumeControl {
123 id: volumeControl
124 }
126 DashCommunicator {
127 id: dash
128 objectName: "dashCommunicator"
129 }
131 Binding {
132 target: ApplicationManager
133 property: "dashActive"
134 value: launcher.shown || launcher.dashSwipe
135 }
138 WindowKeysFilter {
139 // Handle but do not filter out volume keys
140 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
141 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
143 Keys.onPressed: {
144 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
145 dialogs.onPowerKeyPressed();
146 event.accepted = true;
147 } else {
148 event.accepted = false;
149 }
150 }
152 Keys.onReleased: {
153 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
154 dialogs.onPowerKeyReleased();
155 event.accepted = true;
156 } else {
157 event.accepted = false;
158 }
159 }
160 }
162 Item {
163 id: stages
164 objectName: "stages"
165 width: parent.width
166 height: parent.height
167 visible: !ApplicationManager.empty
169 Connections {
170 target: ApplicationManager
171 onFocusRequested: {
172 if (appId === "dialer-app") {
173 // Always let the dialer-app through. Either user asked
174 // for an emergency call or accepted an incoming call.
175 setFakeActiveForApp(appId)
176 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
177 lockscreen.show();
178 }
179 greeter.hide();
180 launcher.hide();
181 }
183 onFocusedApplicationIdChanged: {
184 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
185 lockscreen.show();
186 }
187 panel.indicators.hide();
188 }
190 onApplicationAdded: {
191 if (greeter.shown && appId != "unity8-dash") {
192 greeter.hide();
193 }
194 if (appId === "dialer-app") {
195 // Always let the dialer-app through. Either user asked
196 // for an emergency call or accepted an incoming call.
197 setFakeActiveForApp(appId)
198 }
199 launcher.hide();
200 }
201 }
203 Loader {
204 id: applicationsDisplayLoader
205 anchors.fill: parent
207 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
209 Binding {
210 target: applicationsDisplayLoader.item
211 property: "objectName"
212 value: "stage"
213 }
214 Binding {
215 target: applicationsDisplayLoader.item
216 property: "dragAreaWidth"
217 value: shell.edgeSize
218 }
219 Binding {
220 target: applicationsDisplayLoader.item
221 property: "maximizedAppTopMargin"
222 // Not just using panel.panelHeight as that changes depending on the focused app.
223 value: panel.indicators.panelHeight
224 }
225 Binding {
226 target: applicationsDisplayLoader.item
227 property: "interactive"
228 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
229 }
230 Binding {
231 target: applicationsDisplayLoader.item
232 property: "spreadEnabled"
233 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
234 }
235 Binding {
236 target: applicationsDisplayLoader.item
237 property: "inverseProgress"
238 value: launcher.progress
239 }
240 Binding {
241 target: applicationsDisplayLoader.item
242 property: "orientation"
243 value: shell.orientation
244 }
245 }
246 }
248 InputMethod {
249 id: inputMethod
250 objectName: "inputMethod"
251 anchors { fill: parent; topMargin: panel.panelHeight }
252 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
253 }
255 Connections {
256 target: SurfaceManager
257 onSurfaceCreated: {
258 if (surface.type == MirSurfaceItem.InputMethod) {
259 inputMethod.surface = surface;
260 }
261 }
263 onSurfaceDestroyed: {
264 if (inputMethod.surface == surface) {
265 inputMethod.surface = null;
266 surface.parent = null;
267 }
268 if (!surface.parent) {
269 // there's no one displaying it. delete it right away
270 surface.release();
271 }
272 }
273 }
274 Connections {
275 target: SessionManager
276 onSessionStopping: {
277 if (!session.parentSession && !session.application) {
278 // nothing is using it. delete it right away
279 session.release();
280 }
281 }
282 }
284 Lockscreen {
285 id: lockscreen
286 objectName: "lockscreen"
288 readonly property int backgroundTopMargin: -panel.panelHeight
290 hides: [launcher, panel.indicators]
291 shown: false
292 enabled: true
293 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
294 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
295 y: panel.panelHeight
296 visible: required
297 width: parent.width
298 height: parent.height - panel.panelHeight
299 background: shell.background
300 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
301 minPinLength: 4
302 maxPinLength: 4
304 onEntered: LightDM.Greeter.respond(passphrase);
305 onCancel: greeter.show()
306 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
308 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
310 Component.onCompleted: {
311 if (greeter.narrowMode) {
312 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
313 }
314 }
315 }
317 Connections {
318 target: LightDM.Greeter
320 onShowGreeter: greeter.show()
321 onHideGreeter: greeter.login()
323 onShowPrompt: {
324 if (greeter.narrowMode) {
325 if (isDefaultPrompt) {
326 if (lockscreen.alphaNumeric) {
327 lockscreen.infoText = i18n.tr("Enter your passphrase")
328 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
329 } else {
330 lockscreen.infoText = i18n.tr("Enter your passcode")
331 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
332 }
333 } else {
334 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
335 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
336 }
338 lockscreen.show();
339 }
340 }
342 onPromptlessChanged: {
343 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
344 lockscreen.hide()
345 } else {
346 lockscreen.reset();
347 lockscreen.show();
348 }
349 }
351 onAuthenticationComplete: {
352 if (LightDM.Greeter.authenticated) {
353 AccountsService.failedLogins = 0
354 }
355 // Else only penalize user for a failed login if they actually were
356 // prompted for a password. We do this below after the promptless
357 // early exit.
359 if (LightDM.Greeter.promptless) {
360 return;
361 }
363 if (LightDM.Greeter.authenticated) {
364 greeter.login();
365 } else {
366 AccountsService.failedLogins++
367 if (maxFailedLogins >= 2) { // require at least a warning
368 if (AccountsService.failedLogins === maxFailedLogins - 1) {
369 var title = lockscreen.alphaNumeric ?
370 i18n.tr("Sorry, incorrect passphrase.") :
371 i18n.tr("Sorry, incorrect passcode.")
372 var text = i18n.tr("This will be your last attempt.") + " " +
373 (lockscreen.alphaNumeric ?
374 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
375 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
376 lockscreen.showInfoPopup(title, text)
377 } else if (AccountsService.failedLogins >= maxFailedLogins) {
378 SystemImage.factoryReset() // Ouch!
379 }
380 }
381 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
382 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
383 }
385 lockscreen.clear(true);
386 if (greeter.narrowMode) {
387 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
388 }
389 }
390 }
391 }
393 Binding {
394 target: LightDM.Greeter
395 property: "active"
396 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
397 }
399 Rectangle {
400 anchors.fill: parent
401 color: "black"
402 opacity: greeterWrapper.showProgress * 0.8
403 }
405 Item {
406 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
407 id: greeterWrapper
408 x: launcher.progress
409 y: panel.panelHeight
410 width: parent.width
411 height: parent.height - panel.panelHeight
413 Behavior on x {
414 enabled: !launcher.dashSwipe
415 StandardAnimation {}
416 }
418 property bool fullyShown: showProgress === 1.0
419 onFullyShownChanged: {
420 // Wait until the greeter is completely covering lockscreen before resetting it.
421 if (fullyShown && !LightDM.Greeter.authenticated) {
422 lockscreen.reset();
423 lockscreen.show();
424 }
425 }
427 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
428 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
430 Greeter {
431 id: greeter
432 objectName: "greeter"
434 signal sessionStarted() // helpful for tests
436 property string fakeActiveForApp: ""
438 available: true
439 hides: [launcher, panel.indicators]
440 shown: true
441 loadContent: required || lockscreen.required // keeps content in memory for quick show()
443 defaultBackground: shell.background
445 width: parent.width
446 height: parent.height
448 dragHandleWidth: shell.edgeSize
450 function login() {
451 enabled = false;
452 if (LightDM.Greeter.startSessionSync()) {
453 sessionStarted();
454 greeter.hide();
455 lockscreen.hide();
456 launcher.hide();
457 }
458 enabled = true;
459 }
461 onShownChanged: {
462 if (shown) {
463 if (greeter.narrowMode) {
464 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
465 }
466 greeter.fakeActiveForApp = "";
467 greeter.forceActiveFocus();
468 }
469 }
471 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
472 Component.onCompleted: {
473 Connectivity.unlockAllModems()
474 } */
476 onUnlocked: greeter.hide()
477 onSelected: {
478 // Update launcher items for new user
479 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
480 AccountsService.user = user;
481 LauncherModel.setUser(user);
482 }
484 onTease: launcher.tease()
486 Binding {
487 target: ApplicationManager
488 property: "suspended"
489 value: greeter.shown && greeterWrapper.showProgress == 1
490 }
491 }
492 }
494 Connections {
495 id: powerConnection
496 target: Powerd
498 onStatusChanged: {
499 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
500 greeter.showNow()
501 }
502 }
503 }
505 function showHome() {
506 if (edgeDemo.running) {
507 return
508 }
510 if (LightDM.Greeter.active) {
511 if (!LightDM.Greeter.authenticated) {
512 lockscreen.show()
513 }
514 greeter.hide()
515 }
517 var animate = !LightDM.Greeter.active && !stages.shown
518 dash.setCurrentScope("clickscope", animate, false)
519 ApplicationManager.requestFocusApplication("unity8-dash")
520 }
522 function showDash() {
523 if (shell.locked) {
524 return;
525 }
526 if (greeter.shown) {
527 greeter.hideRight();
528 launcher.fadeOut();
529 }
531 ApplicationManager.requestFocusApplication("unity8-dash")
532 launcher.fadeOut();
533 }
535 Item {
536 id: overlay
537 z: 10
539 anchors.fill: parent
541 Panel {
542 id: panel
543 objectName: "panel"
544 anchors.fill: parent //because this draws indicator menus
545 indicators {
546 hides: [launcher]
547 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
548 contentEnabled: edgeDemo.panelContentEnabled
549 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
550 panelHeight: units.gu(3)
551 }
553 property bool topmostApplicationIsFullscreen:
554 ApplicationManager.focusedApplicationId &&
555 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
557 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
558 || greeter.fakeActiveForApp !== ""
559 }
561 Launcher {
562 id: launcher
563 objectName: "launcher"
565 readonly property bool dashSwipe: progress > 0
567 anchors.top: parent.top
568 anchors.bottom: parent.bottom
569 width: parent.width
570 dragAreaWidth: shell.edgeSize
571 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
573 onShowDashHome: showHome()
574 onDash: {
575 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
576 showDash()
577 }
578 if (greeter.shown) {
579 launcher.fadeOut()
580 greeter.hideRight()
581 }
582 }
583 onDashSwipeChanged: {
584 if (dashSwipe) {
585 dash.setCurrentScope("clickscope", false, true)
586 }
587 }
588 onLauncherApplicationSelected: {
589 if (greeter.fakeActiveForApp !== "") {
590 lockscreen.show()
591 }
592 if (!edgeDemo.running)
593 shell.activateApplication(appId)
594 }
595 onShownChanged: {
596 if (shown) {
597 panel.indicators.hide()
598 }
599 }
600 }
602 Rectangle {
603 id: modalNotificationBackground
605 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
606 color: "#000000"
607 anchors.fill: parent
608 opacity: 0.9
610 MouseArea {
611 anchors.fill: parent
612 }
613 }
615 Notifications {
616 id: notifications
618 model: NotificationBackend.Model
619 margin: units.gu(1)
621 y: panel.panelHeight
622 width: parent.width
623 height: parent.height - panel.panelHeight
625 states: [
626 State {
627 name: "narrow"
628 when: overlay.width <= units.gu(60)
629 AnchorChanges { target: notifications; anchors.left: parent.left }
630 },
631 State {
632 name: "wide"
633 when: overlay.width > units.gu(60)
634 AnchorChanges { target: notifications; anchors.left: undefined }
635 PropertyChanges { target: notifications; width: units.gu(38) }
636 }
637 ]
638 }
639 }
641 Dialogs {
642 id: dialogs
643 anchors.fill: parent
644 z: overlay.z + 10
645 onPowerOffClicked: {
646 shutdownFadeOutRectangle.enabled = true;
647 shutdownFadeOutRectangle.visible = true;
648 shutdownFadeOut.start();
649 }
650 }
652 Label {
653 id: alphaDisclaimerLabel
654 anchors.centerIn: parent
655 visible: ApplicationManager.fake ? ApplicationManager.fake : false
656 z: dialogs.z + 10
657 text: "EARLY ALPHA\nNOT READY FOR USE"
658 color: "lightgrey"
659 opacity: 0.2
660 font.weight: Font.Black
661 horizontalAlignment: Text.AlignHCenter
662 verticalAlignment: Text.AlignVCenter
663 fontSizeMode: Text.Fit
664 rotation: -45
665 scale: Math.min(parent.width, parent.height) / width
666 }
668 EdgeDemo {
669 id: edgeDemo
670 z: alphaDisclaimerLabel.z + 10
671 paused: Powerd.status === Powerd.Off // Saves power
672 greeter: greeter
673 launcher: launcher
674 indicators: panel.indicators
675 stages: stages
676 }
678 Connections {
679 target: SessionBroadcast
680 onShowHome: showHome()
681 }
683 Rectangle {
684 id: shutdownFadeOutRectangle
685 z: edgeDemo.z + 10
686 enabled: false
687 visible: false
688 color: "black"
689 anchors.fill: parent
690 opacity: 0.0
691 NumberAnimation on opacity {
692 id: shutdownFadeOut
693 from: 0.0
694 to: 1.0
695 onStopped: {
696 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
697 DBusUnitySessionService.Shutdown();
698 }
699 }
700 }
701 }