on f16d76e help sign in
unity8/qml/Shell.qml
611 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 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
248 minPinLength: 4
249 maxPinLength: 4
251 onEntered: LightDM.Greeter.respond(passphrase);
252 onCancel: greeter.show()
253 onEmergencyCall: {
254 greeter.fakeActiveForApp = "dialer-app"
255 shell.activateApplication("dialer-app")
256 lockscreen.hide()
257 }
259 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
261 Component.onCompleted: {
262 if (greeter.narrowMode) {
263 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
264 }
265 }
266 }
268 Connections {
269 target: LightDM.Greeter
271 onShowGreeter: greeter.show()
273 onShowPrompt: {
274 if (greeter.narrowMode) {
275 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
276 lockscreen.show();
277 }
278 }
280 onPromptlessChanged: {
281 if (LightDM.Greeter.promptless) {
282 lockscreen.hide()
283 } else {
284 lockscreen.reset();
285 lockscreen.show();
286 }
287 }
289 onAuthenticationComplete: {
290 if (LightDM.Greeter.promptless) {
291 return;
292 }
293 if (LightDM.Greeter.authenticated) {
294 lockscreen.hide();
295 greeter.login();
296 } else {
297 lockscreen.clear(true);
298 if (greeter.narrowMode) {
299 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
300 }
301 }
302 }
303 }
305 Binding {
306 target: LightDM.Greeter
307 property: "active"
308 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
309 }
311 Rectangle {
312 anchors.fill: parent
313 color: "black"
314 opacity: greeterWrapper.showProgress * 0.8
315 }
317 Item {
318 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
319 id: greeterWrapper
320 x: launcher.progress
321 y: panel.panelHeight
322 width: parent.width
323 height: parent.height - panel.panelHeight
325 Behavior on x {
326 enabled: !launcher.dashSwipe
327 StandardAnimation {}
328 }
330 property bool fullyShown: showProgress === 1.0
331 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
332 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
334 Greeter {
335 id: greeter
336 objectName: "greeter"
338 signal sessionStarted() // helpful for tests
340 property string fakeActiveForApp: ""
342 available: true
343 hides: [launcher, panel.indicators]
344 shown: true
346 defaultBackground: shell.background
348 width: parent.width
349 height: parent.height
351 dragHandleWidth: shell.edgeSize
353 function login() {
354 enabled = false;
355 if (LightDM.Greeter.startSessionSync()) {
356 sessionStarted();
357 greeter.hide();
358 lockscreen.hide();
359 launcher.hide();
360 }
361 enabled = true;
362 }
364 onShownChanged: {
365 if (shown) {
366 if (greeter.narrowMode) {
367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
368 }
369 if (!LightDM.Greeter.promptless) {
370 lockscreen.reset();
371 lockscreen.show();
372 }
373 greeter.fakeActiveForApp = "";
374 greeter.forceActiveFocus();
375 }
376 }
378 onUnlocked: greeter.hide()
379 onSelected: {
380 // Update launcher items for new user
381 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
382 AccountsService.user = user;
383 LauncherModel.setUser(user);
384 }
386 onTease: launcher.tease()
388 Binding {
389 target: ApplicationManager
390 property: "suspended"
391 value: greeter.shown && greeterWrapper.showProgress == 1
392 }
393 }
394 }
396 Connections {
397 id: powerConnection
398 target: Powerd
400 onDisplayPowerStateChange: {
401 // We ignore any display-off signals when the proximity sensor
402 // is active. This usually indicates something like a phone call.
403 if (status == Powerd.Off && reason != Powerd.Proximity) {
404 greeter.showNow();
405 }
407 // No reason to chew demo CPU when user isn't watching
408 if (status == Powerd.Off) {
409 edgeDemo.paused = true;
410 } else if (status == Powerd.On) {
411 edgeDemo.paused = false;
412 }
413 }
414 }
416 function showHome() {
417 if (edgeDemo.running) {
418 return
419 }
421 if (LightDM.Greeter.active) {
422 if (!LightDM.Greeter.promptless) {
423 lockscreen.show()
424 }
425 greeter.hide()
426 }
428 var animate = !LightDM.Greeter.active && !stages.shown
429 dash.setCurrentScope("clickscope", animate, false)
430 ApplicationManager.requestFocusApplication("unity8-dash")
431 }
433 function showDash() {
434 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
435 return;
436 }
437 if (greeter.shown) {
438 greeter.hideRight();
439 launcher.fadeOut();
440 }
442 ApplicationManager.requestFocusApplication("unity8-dash")
443 launcher.fadeOut();
444 }
446 Item {
447 id: overlay
448 z: 10
450 anchors.fill: parent
452 Panel {
453 id: panel
454 objectName: "panel"
455 anchors.fill: parent //because this draws indicator menus
456 indicators {
457 hides: [launcher]
458 available: edgeDemo.panelEnabled && greeter.fakeActiveForApp === ""
459 contentEnabled: edgeDemo.panelContentEnabled
460 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
461 panelHeight: units.gu(3)
462 }
464 property bool topmostApplicationIsFullscreen:
465 ApplicationManager.focusedApplicationId &&
466 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
468 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
469 || greeter.fakeActiveForApp !== ""
470 }
472 Launcher {
473 id: launcher
474 objectName: "launcher"
476 readonly property bool dashSwipe: progress > 0
478 anchors.top: parent.top
479 anchors.bottom: parent.bottom
480 width: parent.width
481 dragAreaWidth: shell.edgeSize
482 available: edgeDemo.launcherEnabled && greeter.fakeActiveForApp === ""
484 onShowDashHome: showHome()
485 onDash: showDash()
486 onDashSwipeChanged: {
487 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
488 dash.setCurrentScope("clickscope", false, true)
489 }
490 }
491 onLauncherApplicationSelected: {
492 if (greeter.fakeActiveForApp !== "") {
493 lockscreen.show()
494 }
495 if (!edgeDemo.running)
496 shell.activateApplication(appId)
497 }
498 onShownChanged: {
499 if (shown) {
500 panel.indicators.hide()
501 }
502 }
503 }
505 Rectangle {
506 id: modalNotificationBackground
508 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
509 color: "#000000"
510 anchors.fill: parent
511 opacity: 0.5
513 MouseArea {
514 anchors.fill: parent
515 }
516 }
518 Notifications {
519 id: notifications
521 model: NotificationBackend.Model
522 margin: units.gu(1)
524 y: panel.panelHeight
525 width: parent.width
526 height: parent.height - panel.panelHeight
528 states: [
529 State {
530 name: "narrow"
531 when: overlay.width <= units.gu(60)
532 AnchorChanges { target: notifications; anchors.left: parent.left }
533 },
534 State {
535 name: "wide"
536 when: overlay.width > units.gu(60)
537 AnchorChanges { target: notifications; anchors.left: undefined }
538 PropertyChanges { target: notifications; width: units.gu(38) }
539 }
540 ]
541 }
542 }
544 Binding {
545 target: i18n
546 property: "domain"
547 value: "unity8"
548 }
550 Dialogs {
551 id: dialogs
552 anchors.fill: parent
553 z: overlay.z + 10
554 onPowerOffClicked: {
555 shutdownFadeOutRectangle.enabled = true;
556 shutdownFadeOutRectangle.visible = true;
557 shutdownFadeOut.start();
558 }
559 }
561 Label {
562 id: alphaDisclaimerLabel
563 anchors.centerIn: parent
564 visible: ApplicationManager.fake ? ApplicationManager.fake : false
565 z: dialogs.z + 10
566 text: "EARLY ALPHA\nNOT READY FOR USE"
567 color: "lightgrey"
568 opacity: 0.2
569 font.weight: Font.Black
570 horizontalAlignment: Text.AlignHCenter
571 verticalAlignment: Text.AlignVCenter
572 fontSizeMode: Text.Fit
573 rotation: -45
574 scale: Math.min(parent.width, parent.height) / width
575 }
577 EdgeDemo {
578 id: edgeDemo
579 z: alphaDisclaimerLabel.z + 10
580 greeter: greeter
581 launcher: launcher
582 indicators: panel.indicators
583 underlay: underlay
584 }
586 Connections {
587 target: SessionBroadcast
588 onShowHome: showHome()
589 }
591 Rectangle {
592 id: shutdownFadeOutRectangle
593 z: edgeDemo.z + 10
594 enabled: false
595 visible: false
596 color: "black"
597 anchors.fill: parent
598 opacity: 0.0
599 NumberAnimation on opacity {
600 id: shutdownFadeOut
601 from: 0.0
602 to: 1.0
603 onStopped: {
604 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
605 DBusUnitySessionService.Shutdown();
606 }
607 }
608 }
609 }