on 2917119 help sign in
unity8/qml/Shell.qml
595 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 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated
53 property bool sideStageEnabled: shell.width >= units.gu(100)
54 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
56 function activateApplication(appId) {
57 if (ApplicationManager.findApplication(appId)) {
58 ApplicationManager.requestFocusApplication(appId);
59 } else {
60 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
61 ApplicationManager.startApplication(appId, execFlags);
62 }
63 }
65 Binding {
66 target: LauncherModel
67 property: "applicationManager"
68 value: ApplicationManager
69 }
71 Component.onCompleted: {
72 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
73 }
75 GSettings {
76 id: backgroundSettings
77 schema.id: "org.gnome.desktop.background"
78 }
79 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
80 onGSettingsPictureChanged: {
81 shell.background = gSettingsPicture
82 }
84 VolumeControl {
85 id: volumeControl
86 }
88 DashCommunicator {
89 id: dash
90 objectName: "dashCommunicator"
91 }
93 WindowKeysFilter {
94 // Handle but do not filter out volume keys
95 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
96 Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
98 Keys.onPressed: {
99 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
100 dialogs.onPowerKeyPressed();
101 event.accepted = true;
102 } else {
103 event.accepted = false;
104 }
105 }
107 Keys.onReleased: {
108 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
109 dialogs.onPowerKeyReleased();
110 event.accepted = true;
111 } else {
112 event.accepted = false;
113 }
114 }
115 }
117 Item {
118 id: stages
119 objectName: "stages"
120 width: parent.width
121 height: parent.height
122 visible: !ApplicationManager.empty
124 Connections {
125 target: ApplicationManager
126 onFocusRequested: {
127 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
128 lockscreen.show();
129 }
130 greeter.hide();
131 }
133 onFocusedApplicationIdChanged: {
134 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
135 lockscreen.show();
136 }
137 panel.indicators.hide();
138 }
140 onApplicationAdded: {
141 if (greeter.shown && appId != "unity8-dash") {
142 greeter.hide();
143 }
144 }
145 }
147 Loader {
148 id: applicationsDisplayLoader
149 anchors.fill: parent
151 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
153 Binding {
154 target: applicationsDisplayLoader.item
155 property: "objectName"
156 value: "stage"
157 }
158 Binding {
159 target: applicationsDisplayLoader.item
160 property: "dragAreaWidth"
161 value: shell.edgeSize
162 }
163 Binding {
164 target: applicationsDisplayLoader.item
165 property: "maximizedAppTopMargin"
166 // Not just using panel.panelHeight as that changes depending on the focused app.
167 value: panel.indicators.panelHeight
168 }
169 Binding {
170 target: applicationsDisplayLoader.item
171 property: "interactive"
172 value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
173 }
174 Binding {
175 target: applicationsDisplayLoader.item
176 property: "spreadEnabled"
177 value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
178 }
179 Binding {
180 target: applicationsDisplayLoader.item
181 property: "inverseProgress"
182 value: launcher.progress
183 }
184 }
185 }
187 InputMethod {
188 id: inputMethod
189 objectName: "inputMethod"
190 anchors { fill: parent; topMargin: panel.panelHeight }
191 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
192 }
194 Connections {
195 target: SurfaceManager
196 onSurfaceCreated: {
197 if (surface.type == MirSurfaceItem.InputMethod) {
198 inputMethod.surface = surface;
199 }
200 }
202 onSurfaceDestroyed: {
203 if (inputMethod.surface == surface) {
204 inputMethod.surface = null;
205 surface.parent = null;
206 }
207 if (!surface.parent) {
208 // there's no one displaying it. delete it right away
209 surface.release();
210 }
211 }
212 }
214 Lockscreen {
215 id: lockscreen
216 objectName: "lockscreen"
218 readonly property int backgroundTopMargin: -panel.panelHeight
220 hides: [launcher, panel.indicators]
221 shown: false
222 enabled: true
223 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
224 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
225 y: panel.panelHeight
226 visible: required
227 width: parent.width
228 height: parent.height - panel.panelHeight
229 background: shell.background
230 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
231 minPinLength: 4
232 maxPinLength: 4
234 onEntered: LightDM.Greeter.respond(passphrase);
235 onCancel: greeter.show()
236 onEmergencyCall: {
237 greeter.fakeActiveForApp = "dialer-app"
238 shell.activateApplication("dialer-app")
239 lockscreen.hide()
240 }
242 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
244 Component.onCompleted: {
245 if (greeter.narrowMode) {
246 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
247 }
248 }
249 }
251 Connections {
252 target: LightDM.Greeter
254 onShowGreeter: greeter.show()
256 onShowPrompt: {
257 if (greeter.narrowMode) {
258 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
259 lockscreen.show();
260 }
261 }
263 onPromptlessChanged: {
264 if (LightDM.Greeter.promptless) {
265 lockscreen.hide()
266 } else {
267 lockscreen.reset();
268 lockscreen.show();
269 }
270 }
272 onAuthenticationComplete: {
273 if (LightDM.Greeter.promptless) {
274 return;
275 }
276 if (LightDM.Greeter.authenticated) {
277 lockscreen.hide();
278 greeter.login();
279 } else {
280 lockscreen.clear(true);
281 if (greeter.narrowMode) {
282 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
283 }
284 }
285 }
286 }
288 Binding {
289 target: LightDM.Greeter
290 property: "active"
291 value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
292 }
294 Rectangle {
295 anchors.fill: parent
296 color: "black"
297 opacity: greeterWrapper.showProgress * 0.8
298 }
300 Item {
301 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
302 id: greeterWrapper
303 x: launcher.progress
304 y: panel.panelHeight
305 width: parent.width
306 height: parent.height - panel.panelHeight
308 Behavior on x {
309 enabled: !launcher.dashSwipe
310 StandardAnimation {}
311 }
313 property bool fullyShown: showProgress === 1.0
314 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
315 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
317 Greeter {
318 id: greeter
319 objectName: "greeter"
321 signal sessionStarted() // helpful for tests
323 property string fakeActiveForApp: ""
325 available: true
326 hides: [launcher, panel.indicators]
327 shown: true
328 loadContent: required || lockscreen.required // keeps content in memory for quick show()
330 defaultBackground: shell.background
332 width: parent.width
333 height: parent.height
335 dragHandleWidth: shell.edgeSize
337 function login() {
338 enabled = false;
339 if (LightDM.Greeter.startSessionSync()) {
340 sessionStarted();
341 greeter.hide();
342 lockscreen.hide();
343 launcher.hide();
344 }
345 enabled = true;
346 }
348 onShownChanged: {
349 if (shown) {
350 if (greeter.narrowMode) {
351 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
352 }
353 if (!LightDM.Greeter.promptless) {
354 lockscreen.reset();
355 lockscreen.show();
356 }
357 greeter.fakeActiveForApp = "";
358 greeter.forceActiveFocus();
359 }
360 }
362 onUnlocked: greeter.hide()
363 onSelected: {
364 // Update launcher items for new user
365 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
366 AccountsService.user = user;
367 LauncherModel.setUser(user);
368 }
370 onTease: launcher.tease()
372 Binding {
373 target: ApplicationManager
374 property: "suspended"
375 value: greeter.shown && greeterWrapper.showProgress == 1
376 }
377 }
378 }
380 Connections {
381 id: powerConnection
382 target: Powerd
384 onDisplayPowerStateChange: {
385 // We ignore any display-off signals when the proximity sensor
386 // is active. This usually indicates something like a phone call.
387 if (status == Powerd.Off && reason != Powerd.Proximity && !edgeDemo.running) {
388 greeter.showNow();
389 }
391 // No reason to chew demo CPU when user isn't watching
392 if (status == Powerd.Off) {
393 edgeDemo.paused = true;
394 } else if (status == Powerd.On) {
395 edgeDemo.paused = false;
396 }
397 }
398 }
400 function showHome() {
401 if (edgeDemo.running) {
402 return
403 }
405 if (LightDM.Greeter.active) {
406 if (!LightDM.Greeter.promptless) {
407 lockscreen.show()
408 }
409 greeter.hide()
410 }
412 var animate = !LightDM.Greeter.active && !stages.shown
413 dash.setCurrentScope("clickscope", animate, false)
414 ApplicationManager.requestFocusApplication("unity8-dash")
415 }
417 function showDash() {
418 if (shell.locked) {
419 return;
420 }
421 if (greeter.shown) {
422 greeter.hideRight();
423 launcher.fadeOut();
424 }
426 ApplicationManager.requestFocusApplication("unity8-dash")
427 launcher.fadeOut();
428 }
430 Item {
431 id: overlay
432 z: 10
434 anchors.fill: parent
436 Panel {
437 id: panel
438 objectName: "panel"
439 anchors.fill: parent //because this draws indicator menus
440 indicators {
441 hides: [launcher]
442 available: edgeDemo.panelEnabled && !shell.locked
443 contentEnabled: edgeDemo.panelContentEnabled
444 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
445 panelHeight: units.gu(3)
446 }
448 property bool topmostApplicationIsFullscreen:
449 ApplicationManager.focusedApplicationId &&
450 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
452 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
453 || greeter.fakeActiveForApp !== ""
454 }
456 Launcher {
457 id: launcher
458 objectName: "launcher"
460 readonly property bool dashSwipe: progress > 0
462 anchors.top: parent.top
463 anchors.bottom: parent.bottom
464 width: parent.width
465 dragAreaWidth: shell.edgeSize
466 available: edgeDemo.launcherEnabled && !shell.locked
468 onShowDashHome: showHome()
469 onDash: showDash()
470 onDashSwipeChanged: {
471 if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
472 dash.setCurrentScope("clickscope", false, true)
473 }
474 }
475 onLauncherApplicationSelected: {
476 if (greeter.fakeActiveForApp !== "") {
477 lockscreen.show()
478 }
479 if (!edgeDemo.running)
480 shell.activateApplication(appId)
481 }
482 onShownChanged: {
483 if (shown) {
484 panel.indicators.hide()
485 }
486 }
487 }
489 Rectangle {
490 id: modalNotificationBackground
492 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
493 color: "#000000"
494 anchors.fill: parent
495 opacity: 0.5
497 MouseArea {
498 anchors.fill: parent
499 }
500 }
502 Notifications {
503 id: notifications
505 model: NotificationBackend.Model
506 margin: units.gu(1)
508 y: panel.panelHeight
509 width: parent.width
510 height: parent.height - panel.panelHeight
512 states: [
513 State {
514 name: "narrow"
515 when: overlay.width <= units.gu(60)
516 AnchorChanges { target: notifications; anchors.left: parent.left }
517 },
518 State {
519 name: "wide"
520 when: overlay.width > units.gu(60)
521 AnchorChanges { target: notifications; anchors.left: undefined }
522 PropertyChanges { target: notifications; width: units.gu(38) }
523 }
524 ]
525 }
526 }
528 Binding {
529 target: i18n
530 property: "domain"
531 value: "unity8"
532 }
534 Dialogs {
535 id: dialogs
536 anchors.fill: parent
537 z: overlay.z + 10
538 onPowerOffClicked: {
539 shutdownFadeOutRectangle.enabled = true;
540 shutdownFadeOutRectangle.visible = true;
541 shutdownFadeOut.start();
542 }
543 }
545 Label {
546 id: alphaDisclaimerLabel
547 anchors.centerIn: parent
548 visible: ApplicationManager.fake ? ApplicationManager.fake : false
549 z: dialogs.z + 10
550 text: "EARLY ALPHA\nNOT READY FOR USE"
551 color: "lightgrey"
552 opacity: 0.2
553 font.weight: Font.Black
554 horizontalAlignment: Text.AlignHCenter
555 verticalAlignment: Text.AlignVCenter
556 fontSizeMode: Text.Fit
557 rotation: -45
558 scale: Math.min(parent.width, parent.height) / width
559 }
561 EdgeDemo {
562 id: edgeDemo
563 z: alphaDisclaimerLabel.z + 10
564 greeter: greeter
565 launcher: launcher
566 indicators: panel.indicators
567 stages: stages
568 }
570 Connections {
571 target: SessionBroadcast
572 onShowHome: showHome()
573 }
575 Rectangle {
576 id: shutdownFadeOutRectangle
577 z: edgeDemo.z + 10
578 enabled: false
579 visible: false
580 color: "black"
581 anchors.fill: parent
582 opacity: 0.0
583 NumberAnimation on opacity {
584 id: shutdownFadeOut
585 from: 0.0
586 to: 1.0
587 onStopped: {
588 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
589 DBusUnitySessionService.Shutdown();
590 }
591 }
592 }
593 }