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