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