on 0eef252 help sign in
unity8/qml/Shell.qml
692 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 (shell.locked) {
517 return;
518 }
519 if (greeter.shown) {
520 greeter.hideRight();
521 launcher.fadeOut();
522 }
524 ApplicationManager.requestFocusApplication("unity8-dash")
525 launcher.fadeOut();
526 }
528 Item {
529 id: overlay
530 z: 10
532 anchors.fill: parent
534 Panel {
535 id: panel
536 objectName: "panel"
537 anchors.fill: parent //because this draws indicator menus
538 indicators {
539 hides: [launcher]
540 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
541 contentEnabled: edgeDemo.panelContentEnabled
542 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
543 panelHeight: units.gu(3)
544 }
546 property bool topmostApplicationIsFullscreen:
547 ApplicationManager.focusedApplicationId &&
548 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
550 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
551 || greeter.fakeActiveForApp !== ""
552 }
554 Launcher {
555 id: launcher
556 objectName: "launcher"
558 readonly property bool dashSwipe: progress > 0
560 anchors.top: parent.top
561 anchors.bottom: parent.bottom
562 width: parent.width
563 dragAreaWidth: shell.edgeSize
564 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
566 onShowDashHome: showHome()
567 onDash: {
568 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
569 showDash()
570 }
571 }
572 onDashSwipeChanged: {
573 if (dashSwipe) {
574 dash.setCurrentScope("clickscope", false, true)
575 }
576 }
577 onLauncherApplicationSelected: {
578 if (greeter.fakeActiveForApp !== "") {
579 lockscreen.show()
580 }
581 if (!edgeDemo.running)
582 shell.activateApplication(appId)
583 }
584 onShownChanged: {
585 if (shown) {
586 panel.indicators.hide()
587 }
588 }
589 }
591 Rectangle {
592 id: modalNotificationBackground
594 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
595 color: "#000000"
596 anchors.fill: parent
597 opacity: 0.9
599 MouseArea {
600 anchors.fill: parent
601 }
602 }
604 Notifications {
605 id: notifications
607 model: NotificationBackend.Model
608 margin: units.gu(1)
610 y: panel.panelHeight
611 width: parent.width
612 height: parent.height - panel.panelHeight
614 states: [
615 State {
616 name: "narrow"
617 when: overlay.width <= units.gu(60)
618 AnchorChanges { target: notifications; anchors.left: parent.left }
619 },
620 State {
621 name: "wide"
622 when: overlay.width > units.gu(60)
623 AnchorChanges { target: notifications; anchors.left: undefined }
624 PropertyChanges { target: notifications; width: units.gu(38) }
625 }
626 ]
627 }
628 }
630 Dialogs {
631 id: dialogs
632 anchors.fill: parent
633 z: overlay.z + 10
634 onPowerOffClicked: {
635 shutdownFadeOutRectangle.enabled = true;
636 shutdownFadeOutRectangle.visible = true;
637 shutdownFadeOut.start();
638 }
639 }
641 Label {
642 id: alphaDisclaimerLabel
643 anchors.centerIn: parent
644 visible: ApplicationManager.fake ? ApplicationManager.fake : false
645 z: dialogs.z + 10
646 text: "EARLY ALPHA\nNOT READY FOR USE"
647 color: "lightgrey"
648 opacity: 0.2
649 font.weight: Font.Black
650 horizontalAlignment: Text.AlignHCenter
651 verticalAlignment: Text.AlignVCenter
652 fontSizeMode: Text.Fit
653 rotation: -45
654 scale: Math.min(parent.width, parent.height) / width
655 }
657 EdgeDemo {
658 id: edgeDemo
659 z: alphaDisclaimerLabel.z + 10
660 paused: Powerd.status === Powerd.Off // Saves power
661 greeter: greeter
662 launcher: launcher
663 indicators: panel.indicators
664 stages: stages
665 }
667 Connections {
668 target: SessionBroadcast
669 onShowHome: showHome()
670 }
672 Rectangle {
673 id: shutdownFadeOutRectangle
674 z: edgeDemo.z + 10
675 enabled: false
676 visible: false
677 color: "black"
678 anchors.fill: parent
679 opacity: 0.0
680 NumberAnimation on opacity {
681 id: shutdownFadeOut
682 from: 0.0
683 to: 1.0
684 onStopped: {
685 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
686 DBusUnitySessionService.Shutdown();
687 }
688 }
689 }
690 }