on ba0dba7 help sign in
unity8/qml/Shell.qml
691 lines 23 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 WindowKeysFilter {
132 // Handle but do not filter out volume keys
133 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
134 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
136 Keys.onPressed: {
137 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
138 dialogs.onPowerKeyPressed();
139 event.accepted = true;
140 } else {
141 event.accepted = false;
142 }
143 }
145 Keys.onReleased: {
146 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
147 dialogs.onPowerKeyReleased();
148 event.accepted = true;
149 } else {
150 event.accepted = false;
151 }
152 }
153 }
155 Item {
156 id: stages
157 objectName: "stages"
158 width: parent.width
159 height: parent.height
160 visible: !ApplicationManager.empty
162 Connections {
163 target: ApplicationManager
164 onFocusRequested: {
165 if (appId === "dialer-app") {
166 // Always let the dialer-app through. Either user asked
167 // for an emergency call or accepted an incoming call.
168 setFakeActiveForApp(appId)
169 } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
170 lockscreen.show();
171 }
172 greeter.hide();
173 launcher.hide();
174 }
176 onFocusedApplicationIdChanged: {
177 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
178 lockscreen.show();
179 }
180 panel.indicators.hide();
181 }
183 onApplicationAdded: {
184 if (greeter.shown && appId != "unity8-dash") {
185 greeter.hide();
186 }
187 if (appId === "dialer-app") {
188 // Always let the dialer-app through. Either user asked
189 // for an emergency call or accepted an incoming call.
190 setFakeActiveForApp(appId)
191 }
192 launcher.hide();
193 }
194 }
196 Loader {
197 id: applicationsDisplayLoader
198 anchors.fill: parent
200 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
202 Binding {
203 target: applicationsDisplayLoader.item
204 property: "objectName"
205 value: "stage"
206 }
207 Binding {
208 target: applicationsDisplayLoader.item
209 property: "dragAreaWidth"
210 value: shell.edgeSize
211 }
212 Binding {
213 target: applicationsDisplayLoader.item
214 property: "maximizedAppTopMargin"
215 // Not just using panel.panelHeight as that changes depending on the focused app.
216 value: panel.indicators.panelHeight
217 }
218 Binding {
219 target: applicationsDisplayLoader.item
220 property: "interactive"
221 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
222 }
223 Binding {
224 target: applicationsDisplayLoader.item
225 property: "spreadEnabled"
226 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
227 }
228 Binding {
229 target: applicationsDisplayLoader.item
230 property: "inverseProgress"
231 value: launcher.progress
232 }
233 Binding {
234 target: applicationsDisplayLoader.item
235 property: "orientation"
236 value: shell.orientation
237 }
238 }
239 }
241 InputMethod {
242 id: inputMethod
243 objectName: "inputMethod"
244 anchors { fill: parent; topMargin: panel.panelHeight }
245 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
246 }
248 Connections {
249 target: SurfaceManager
250 onSurfaceCreated: {
251 if (surface.type == MirSurfaceItem.InputMethod) {
252 inputMethod.surface = surface;
253 }
254 }
256 onSurfaceDestroyed: {
257 if (inputMethod.surface == surface) {
258 inputMethod.surface = null;
259 surface.parent = null;
260 }
261 if (!surface.parent) {
262 // there's no one displaying it. delete it right away
263 surface.release();
264 }
265 }
266 }
267 Connections {
268 target: SessionManager
269 onSessionStopping: {
270 if (!session.parentSession && !session.application) {
271 // nothing is using it. delete it right away
272 session.release();
273 }
274 }
275 }
277 Lockscreen {
278 id: lockscreen
279 objectName: "lockscreen"
281 readonly property int backgroundTopMargin: -panel.panelHeight
283 hides: [launcher, panel.indicators]
284 shown: false
285 enabled: true
286 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
287 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
288 y: panel.panelHeight
289 visible: required
290 width: parent.width
291 height: parent.height - panel.panelHeight
292 background: shell.background
293 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
294 minPinLength: 4
295 maxPinLength: 4
297 onEntered: LightDM.Greeter.respond(passphrase);
298 onCancel: greeter.show()
299 onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
301 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
303 Component.onCompleted: {
304 if (greeter.narrowMode) {
305 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
306 }
307 }
308 }
310 Connections {
311 target: LightDM.Greeter
313 onShowGreeter: greeter.show()
314 onHideGreeter: greeter.login()
316 onShowPrompt: {
317 if (greeter.narrowMode) {
318 if (isDefaultPrompt) {
319 if (lockscreen.alphaNumeric) {
320 lockscreen.infoText = i18n.tr("Enter your passphrase")
321 lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
322 } else {
323 lockscreen.infoText = i18n.tr("Enter your passcode")
324 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
325 }
326 } else {
327 lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
328 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
329 }
331 lockscreen.show();
332 }
333 }
335 onPromptlessChanged: {
336 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
337 lockscreen.hide()
338 } else {
339 lockscreen.reset();
340 lockscreen.show();
341 }
342 }
344 onAuthenticationComplete: {
345 if (LightDM.Greeter.authenticated) {
346 AccountsService.failedLogins = 0
347 }
348 // Else only penalize user for a failed login if they actually were
349 // prompted for a password. We do this below after the promptless
350 // early exit.
352 if (LightDM.Greeter.promptless) {
353 return;
354 }
356 if (LightDM.Greeter.authenticated) {
357 greeter.login();
358 } else {
359 AccountsService.failedLogins++
360 if (maxFailedLogins >= 2) { // require at least a warning
361 if (AccountsService.failedLogins === maxFailedLogins - 1) {
362 var title = lockscreen.alphaNumeric ?
363 i18n.tr("Sorry, incorrect passphrase.") :
364 i18n.tr("Sorry, incorrect passcode.")
365 var text = i18n.tr("This will be your last attempt.") + " " +
366 (lockscreen.alphaNumeric ?
367 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
368 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
369 lockscreen.showInfoPopup(title, text)
370 } else if (AccountsService.failedLogins >= maxFailedLogins) {
371 SystemImage.factoryReset() // Ouch!
372 }
373 }
374 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
375 lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
376 }
378 lockscreen.clear(true);
379 if (greeter.narrowMode) {
380 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
381 }
382 }
383 }
384 }
386 Binding {
387 target: LightDM.Greeter
388 property: "active"
389 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
390 }
392 Rectangle {
393 anchors.fill: parent
394 color: "black"
395 opacity: greeterWrapper.showProgress * 0.8
396 }
398 Item {
399 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
400 id: greeterWrapper
401 x: launcher.progress
402 y: panel.panelHeight
403 width: parent.width
404 height: parent.height - panel.panelHeight
406 Behavior on x {
407 enabled: !launcher.dashSwipe
408 StandardAnimation {}
409 }
411 property bool fullyShown: showProgress === 1.0
412 onFullyShownChanged: {
413 // Wait until the greeter is completely covering lockscreen before resetting it.
414 if (fullyShown && !LightDM.Greeter.authenticated) {
415 lockscreen.reset();
416 lockscreen.show();
417 }
418 }
420 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
421 onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
423 Greeter {
424 id: greeter
425 objectName: "greeter"
427 signal sessionStarted() // helpful for tests
429 property string fakeActiveForApp: ""
431 available: true
432 hides: [launcher, panel.indicators]
433 shown: true
434 loadContent: required || lockscreen.required // keeps content in memory for quick show()
436 defaultBackground: shell.background
438 width: parent.width
439 height: parent.height
441 dragHandleWidth: shell.edgeSize
443 function login() {
444 enabled = false;
445 if (LightDM.Greeter.startSessionSync()) {
446 sessionStarted();
447 greeter.hide();
448 lockscreen.hide();
449 launcher.hide();
450 }
451 enabled = true;
452 }
454 onShownChanged: {
455 if (shown) {
456 if (greeter.narrowMode) {
457 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
458 }
459 greeter.fakeActiveForApp = "";
460 greeter.forceActiveFocus();
461 }
462 }
464 /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
465 Component.onCompleted: {
466 Connectivity.unlockAllModems()
467 } */
469 onUnlocked: greeter.hide()
470 onSelected: {
471 // Update launcher items for new user
472 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
473 AccountsService.user = user;
474 LauncherModel.setUser(user);
475 }
477 onTease: launcher.tease()
479 Binding {
480 target: ApplicationManager
481 property: "suspended"
482 value: greeter.shown && greeterWrapper.showProgress == 1
483 }
484 }
485 }
487 Connections {
488 id: powerConnection
489 target: Powerd
491 onStatusChanged: {
492 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
493 greeter.showNow()
494 }
495 }
496 }
498 function showHome() {
499 if (edgeDemo.running) {
500 return
501 }
503 if (LightDM.Greeter.active) {
504 if (!LightDM.Greeter.authenticated) {
505 lockscreen.show()
506 }
507 greeter.hide()
508 }
510 var animate = !LightDM.Greeter.active && !stages.shown
511 dash.setCurrentScope("clickscope", animate, false)
512 ApplicationManager.requestFocusApplication("unity8-dash")
513 }
515 function showDash() {
516 if (greeter.fakeActiveForApp !== "") { // just in case user gets here
517 return
518 }
520 if (greeter.shown) {
521 greeter.hideRight();
522 launcher.fadeOut();
523 }
525 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
526 ApplicationManager.requestFocusApplication("unity8-dash")
527 launcher.fadeOut();
528 }
529 }
531 Item {
532 id: overlay
533 z: 10
535 anchors.fill: parent
537 Panel {
538 id: panel
539 objectName: "panel"
540 anchors.fill: parent //because this draws indicator menus
541 indicators {
542 hides: [launcher]
543 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
544 contentEnabled: edgeDemo.panelContentEnabled
545 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
546 panelHeight: units.gu(3)
547 }
549 property bool topmostApplicationIsFullscreen:
550 ApplicationManager.focusedApplicationId &&
551 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
553 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
554 || greeter.fakeActiveForApp !== ""
555 }
557 Launcher {
558 id: launcher
559 objectName: "launcher"
561 readonly property bool dashSwipe: progress > 0
563 anchors.top: parent.top
564 anchors.bottom: parent.bottom
565 width: parent.width
566 dragAreaWidth: shell.edgeSize
567 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
569 onShowDashHome: showHome()
570 onDash: showDash()
571 onDashSwipeChanged: {
572 if (dashSwipe) {
573 dash.setCurrentScope("clickscope", false, true)
574 }
575 }
576 onLauncherApplicationSelected: {
577 if (greeter.fakeActiveForApp !== "") {
578 lockscreen.show()
579 }
580 if (!edgeDemo.running)
581 shell.activateApplication(appId)
582 }
583 onShownChanged: {
584 if (shown) {
585 panel.indicators.hide()
586 }
587 }
588 }
590 Rectangle {
591 id: modalNotificationBackground
593 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
594 color: "#000000"
595 anchors.fill: parent
596 opacity: 0.9
598 MouseArea {
599 anchors.fill: parent
600 }
601 }
603 Notifications {
604 id: notifications
606 model: NotificationBackend.Model
607 margin: units.gu(1)
609 y: panel.panelHeight
610 width: parent.width
611 height: parent.height - panel.panelHeight
613 states: [
614 State {
615 name: "narrow"
616 when: overlay.width <= units.gu(60)
617 AnchorChanges { target: notifications; anchors.left: parent.left }
618 },
619 State {
620 name: "wide"
621 when: overlay.width > units.gu(60)
622 AnchorChanges { target: notifications; anchors.left: undefined }
623 PropertyChanges { target: notifications; width: units.gu(38) }
624 }
625 ]
626 }
627 }
629 Dialogs {
630 id: dialogs
631 anchors.fill: parent
632 z: overlay.z + 10
633 onPowerOffClicked: {
634 shutdownFadeOutRectangle.enabled = true;
635 shutdownFadeOutRectangle.visible = true;
636 shutdownFadeOut.start();
637 }
638 }
640 Label {
641 id: alphaDisclaimerLabel
642 anchors.centerIn: parent
643 visible: ApplicationManager.fake ? ApplicationManager.fake : false
644 z: dialogs.z + 10
645 text: "EARLY ALPHA\nNOT READY FOR USE"
646 color: "lightgrey"
647 opacity: 0.2
648 font.weight: Font.Black
649 horizontalAlignment: Text.AlignHCenter
650 verticalAlignment: Text.AlignVCenter
651 fontSizeMode: Text.Fit
652 rotation: -45
653 scale: Math.min(parent.width, parent.height) / width
654 }
656 EdgeDemo {
657 id: edgeDemo
658 z: alphaDisclaimerLabel.z + 10
659 paused: Powerd.status === Powerd.Off // Saves power
660 greeter: greeter
661 launcher: launcher
662 indicators: panel.indicators
663 stages: stages
664 }
666 Connections {
667 target: SessionBroadcast
668 onShowHome: showHome()
669 }
671 Rectangle {
672 id: shutdownFadeOutRectangle
673 z: edgeDemo.z + 10
674 enabled: false
675 visible: false
676 color: "black"
677 anchors.fill: parent
678 opacity: 0.0
679 NumberAnimation on opacity {
680 id: shutdownFadeOut
681 from: 0.0
682 to: 1.0
683 onStopped: {
684 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
685 DBusUnitySessionService.Shutdown();
686 }
687 }
688 }
689 }