on aafc6d8 help sign in
unity8/qml/Shell.qml
620 lines 19 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.Gestures 0.1
23import Ubuntu.Telephony 0.1 as Telephony
24import Unity.Launcher 0.1
25import Utils 0.1
26import LightDM 0.1 as LightDM
27import Powerd 0.1
28import SessionBroadcast 0.1
29import "Greeter"
30import "Launcher"
31import "Panel"
32import "Components"
33import "Notifications"
34import "Stages"
35import Unity.Notifications 1.0 as NotificationBackend
36import Unity.Session 0.1
37import Unity.DashCommunicator 0.1
39Item {
40 id: shell
42 // this is only here to select the width / height of the window if not running fullscreen
43 property bool tablet: false
44 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
45 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
47 property real edgeSize: units.gu(2)
48 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
49 property url background
50 readonly property real panelHeight: panel.panelHeight
52 property bool sideStageEnabled: shell.width >= units.gu(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 } else {
59 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
60 ApplicationManager.startApplication(appId, execFlags);
61 }
62 }
64 Binding {
65 target: LauncherModel
66 property: "applicationManager"
67 value: ApplicationManager
68 }
70 Component.onCompleted: {
71 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
72 }
74 GSettings {
75 id: backgroundSettings
76 schema.id: "org.gnome.desktop.background"
77 }
78 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
79 onGSettingsPictureChanged: {
80 shell.background = gSettingsPicture
81 }
83 VolumeControl {
84 id: volumeControl
85 }
87 DashCommunicator {
88 id: dash
89 objectName: "dashCommunicator"
90 }
92 WindowKeysFilter {
93 // Handle but do not filter out volume keys
94 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
95 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
97 Keys.onPressed: {
98 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
99 dialogs.onPowerKeyPressed();
100 event.accepted = true;
101 } else {
102 event.accepted = false;
103 }
104 }
106 Keys.onReleased: {
107 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
108 dialogs.onPowerKeyReleased();
109 event.accepted = true;
110 } else {
111 event.accepted = false;
112 }
113 }
114 }
116 Item {
117 id: underlay
118 objectName: "underlay"
119 anchors.fill: parent
121 // Whether the underlay is fully covered by opaque UI elements.
122 property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
123 || stages.fullyShown || greeterWrapper.fullyShown
124 visible: !fullyCovered
126 Image {
127 anchors.fill: underlay
128 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
129 fillMode: Image.PreserveAspectCrop
130 horizontalAlignment: Image.AlignRight
131 verticalAlignment: Image.AlignTop
132 }
133 }
135 Item {
136 id: stages
137 objectName: "stages"
138 width: parent.width
139 height: parent.height
140 visible: !ApplicationManager.empty
142 Connections {
143 target: ApplicationManager
144 onFocusRequested: {
145 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
146 lockscreen.show();
147 }
148 greeter.hide();
149 }
151 onFocusedApplicationIdChanged: {
152 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
153 lockscreen.show();
154 }
155 panel.indicators.hide();
156 }
158 onApplicationAdded: {
159 if (greeter.shown && appId != "unity8-dash") {
160 greeter.hide();
161 }
162 }
163 }
165 Loader {
166 id: applicationsDisplayLoader
167 anchors.fill: parent
169 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
171 Binding {
172 target: applicationsDisplayLoader.item
173 property: "objectName"
174 value: "stage"
175 }
176 Binding {
177 target: applicationsDisplayLoader.item
178 property: "dragAreaWidth"
179 value: shell.edgeSize
180 }
181 Binding {
182 target: applicationsDisplayLoader.item
183 property: "maximizedAppTopMargin"
184 // Not just using panel.panelHeight as that changes depending on the focused app.
185 value: panel.indicators.panelHeight
186 }
187 Binding {
188 target: applicationsDisplayLoader.item
189 property: "interactive"
190 value: !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
191 }
192 Binding {
193 target: applicationsDisplayLoader.item
194 property: "spreadEnabled"
195 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
196 }
197 Binding {
198 target: applicationsDisplayLoader.item
199 property: "inverseProgress"
200 value: launcher.progress
201 }
202 }
203 }
205 InputMethod {
206 id: inputMethod
207 objectName: "inputMethod"
208 anchors { fill: parent; topMargin: panel.panelHeight }
209 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
210 }
212 Connections {
213 target: SurfaceManager
214 onSurfaceCreated: {
215 if (surface.type == MirSurfaceItem.InputMethod) {
216 inputMethod.surface = surface;
217 }
218 }
220 onSurfaceDestroyed: {
221 if (inputMethod.surface == surface) {
222 inputMethod.surface = null;
223 surface.parent = null;
224 }
225 if (!surface.parent) {
226 // there's no one displaying it. delete it right away
227 surface.release();
228 }
229 }
230 }
232 Lockscreen {
233 id: lockscreen
234 objectName: "lockscreen"
236 readonly property int backgroundTopMargin: -panel.panelHeight
238 hides: [launcher, panel.indicators]
239 shown: false
240 enabled: true
241 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
242 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
243 y: panel.panelHeight
244 visible: required
245 width: parent.width
246 height: parent.height - panel.panelHeight
247 background: shell.background
248 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
249 minPinLength: 4
250 maxPinLength: 4
252 onEntered: LightDM.Greeter.respond(passphrase);
253 onCancel: greeter.show()
254 onEmergencyCall: {
255 greeter.fakeActiveForApp = "dialer-app"
256 shell.activateApplication("dialer-app")
257 lockscreen.hide()
258 }
260 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
262 Component.onCompleted: {
263 if (greeter.narrowMode) {
264 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
265 }
266 }
267 }
269 Connections {
270 target: LightDM.Greeter
272 onShowGreeter: greeter.show()
274 onShowPrompt: {
275 if (greeter.narrowMode) {
276 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
277 lockscreen.show();
278 }
279 }
281 onPromptlessChanged: {
282 if (LightDM.Greeter.promptless) {
283 lockscreen.hide()
284 } else {
285 lockscreen.reset();
286 lockscreen.show();
287 }
288 }
290 onAuthenticationComplete: {
291 if (LightDM.Greeter.promptless) {
292 return;
293 }
294 if (LightDM.Greeter.authenticated) {
295 lockscreen.hide();
296 greeter.login();
297 } else {
298 lockscreen.clear(true);
299 if (greeter.narrowMode) {
300 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
301 }
302 }
303 }
304 }
306 Binding {
307 target: LightDM.Greeter
308 property: "active"
309 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
310 }
312 Rectangle {
313 anchors.fill: parent
314 color: "black"
315 opacity: greeterWrapper.showProgress * 0.8
316 }
318 Item {
319 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
320 id: greeterWrapper
321 x: launcher.progress
322 y: panel.panelHeight
323 width: parent.width
324 height: parent.height - panel.panelHeight
326 Behavior on x {
327 enabled: !launcher.dashSwipe
328 StandardAnimation {}
329 }
331 property bool fullyShown: showProgress === 1.0
332 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
333 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
335 Greeter {
336 id: greeter
337 objectName: "greeter"
339 signal sessionStarted() // helpful for tests
341 property string fakeActiveForApp: ""
343 available: true
344 hides: [launcher, panel.indicators]
345 shown: true
347 defaultBackground: shell.background
349 width: parent.width
350 height: parent.height
352 dragHandleWidth: shell.edgeSize
354 function login() {
355 enabled = false;
356 if (LightDM.Greeter.startSessionSync()) {
357 sessionStarted();
358 greeter.hide();
359 lockscreen.hide();
360 launcher.hide();
361 }
362 enabled = true;
363 }
365 onShownChanged: {
366 if (shown) {
367 if (greeter.narrowMode) {
368 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
369 }
370 if (!LightDM.Greeter.promptless) {
371 lockscreen.reset();
372 lockscreen.show();
373 }
374 greeter.fakeActiveForApp = "";
375 greeter.forceActiveFocus();
376 }
377 }
379 onUnlocked: greeter.hide()
380 onSelected: {
381 // Update launcher items for new user
382 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
383 AccountsService.user = user;
384 LauncherModel.setUser(user);
385 }
387 onTease: launcher.tease()
389 Binding {
390 target: ApplicationManager
391 property: "suspended"
392 value: greeter.shown && greeterWrapper.showProgress == 1
393 }
394 }
395 }
397 function showGreeterIfScreenIsOff() {
398 if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
399 greeter.showNow()
400 }
401 }
403 Connections {
404 id: callConnection
405 target: callManager
406 onHasCallsChanged: showGreeterIfScreenIsOff()
407 }
409 Connections {
410 id: powerConnection
411 target: Powerd
413 onStatusChanged: {
414 showGreeterIfScreenIsOff()
416 // No reason to chew demo CPU when user isn't watching
417 if (Powerd.status === Powerd.Off) {
418 edgeDemo.paused = true;
419 } else if (Powerd.status === Powerd.On) {
420 edgeDemo.paused = false;
421 }
422 }
423 }
425 function showHome() {
426 if (edgeDemo.running) {
427 return
428 }
430 if (LightDM.Greeter.active) {
431 if (!LightDM.Greeter.promptless) {
432 lockscreen.show()
433 }
434 greeter.hide()
435 }
437 var animate = !LightDM.Greeter.active && !stages.shown
438 dash.setCurrentScope("clickscope", animate, false)
439 ApplicationManager.requestFocusApplication("unity8-dash")
440 }
442 function showDash() {
443 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
444 return;
445 }
446 if (greeter.shown) {
447 greeter.hideRight();
448 launcher.fadeOut();
449 }
451 ApplicationManager.requestFocusApplication("unity8-dash")
452 launcher.fadeOut();
453 }
455 Item {
456 id: overlay
457 z: 10
459 anchors.fill: parent
461 Panel {
462 id: panel
463 objectName: "panel"
464 anchors.fill: parent //because this draws indicator menus
465 indicators {
466 hides: [launcher]
467 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
468 contentEnabled: edgeDemo.panelContentEnabled
469 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
470 panelHeight: units.gu(3)
471 }
473 property bool topmostApplicationIsFullscreen:
474 ApplicationManager.focusedApplicationId &&
475 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
477 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
478 || greeter.fakeActiveForApp !== ""
479 }
481 Launcher {
482 id: launcher
483 objectName: "launcher"
485 readonly property bool dashSwipe: progress > 0
487 anchors.top: parent.top
488 anchors.bottom: parent.bottom
489 width: parent.width
490 dragAreaWidth: shell.edgeSize
491 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
493 onShowDashHome: showHome()
494 onDash: showDash()
495 onDashSwipeChanged: {
496 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
497 dash.setCurrentScope("clickscope", false, true)
498 }
499 }
500 onLauncherApplicationSelected: {
501 if (greeter.fakeActiveForApp !== "") {
502 lockscreen.show()
503 }
504 if (!edgeDemo.running)
505 shell.activateApplication(appId)
506 }
507 onShownChanged: {
508 if (shown) {
509 panel.indicators.hide()
510 }
511 }
512 }
514 Rectangle {
515 id: modalNotificationBackground
517 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
518 color: "#000000"
519 anchors.fill: parent
520 opacity: 0.5
522 MouseArea {
523 anchors.fill: parent
524 }
525 }
527 Notifications {
528 id: notifications
530 model: NotificationBackend.Model
531 margin: units.gu(1)
533 y: panel.panelHeight
534 width: parent.width
535 height: parent.height - panel.panelHeight
537 states: [
538 State {
539 name: "narrow"
540 when: overlay.width <= units.gu(60)
541 AnchorChanges { target: notifications; anchors.left: parent.left }
542 },
543 State {
544 name: "wide"
545 when: overlay.width > units.gu(60)
546 AnchorChanges { target: notifications; anchors.left: undefined }
547 PropertyChanges { target: notifications; width: units.gu(38) }
548 }
549 ]
550 }
551 }
553 Binding {
554 target: i18n
555 property: "domain"
556 value: "unity8"
557 }
559 Dialogs {
560 id: dialogs
561 anchors.fill: parent
562 z: overlay.z + 10
563 onPowerOffClicked: {
564 shutdownFadeOutRectangle.enabled = true;
565 shutdownFadeOutRectangle.visible = true;
566 shutdownFadeOut.start();
567 }
568 }
570 Label {
571 id: alphaDisclaimerLabel
572 anchors.centerIn: parent
573 visible: ApplicationManager.fake ? ApplicationManager.fake : false
574 z: dialogs.z + 10
575 text: "EARLY ALPHA\nNOT READY FOR USE"
576 color: "lightgrey"
577 opacity: 0.2
578 font.weight: Font.Black
579 horizontalAlignment: Text.AlignHCenter
580 verticalAlignment: Text.AlignVCenter
581 fontSizeMode: Text.Fit
582 rotation: -45
583 scale: Math.min(parent.width, parent.height) / width
584 }
586 EdgeDemo {
587 id: edgeDemo
588 z: alphaDisclaimerLabel.z + 10
589 greeter: greeter
590 launcher: launcher
591 indicators: panel.indicators
592 underlay: underlay
593 }
595 Connections {
596 target: SessionBroadcast
597 onShowHome: showHome()
598 }
600 Rectangle {
601 id: shutdownFadeOutRectangle
602 z: edgeDemo.z + 10
603 enabled: false
604 visible: false
605 color: "black"
606 anchors.fill: parent
607 opacity: 0.0
608 NumberAnimation on opacity {
609 id: shutdownFadeOut
610 from: 0.0
611 to: 1.0
612 onStopped: {
613 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
614 DBusUnitySessionService.Shutdown();
615 }
616 }
617 }
618 }