on b1e092a help sign in
unity8/qml/Shell.qml
576 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 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.Telephony 0.1 as Telephony
26import Unity.Connectivity 0.1
27import Unity.Launcher 0.1
28import Utils 0.1
29import LightDM 0.1 as LightDM
30import Powerd 0.1
31import SessionBroadcast 0.1
32import "Greeter"
33import "Launcher"
34import "Panel"
35import "Components"
36import "Notifications"
37import "Stages"
38import "Panel/Indicators"
39import "Wizard"
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 enabled: !greeter.waiting
54 property real edgeSize: units.gu(2)
55 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
56 property url background: asImageTester.status == Image.Ready ? asImageTester.source
57 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
58 readonly property real panelHeight: panel.panelHeight
60 readonly property alias locked: greeter.locked
62 property bool sideStageEnabled: shell.width >= units.gu(100)
63 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
65 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
66 property int failedLoginsDelayAttempts: 7 // number of failed logins
67 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
69 property int orientation
70 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
71 onDeviceOrientationAngleChanged: {
72 if (!OrientationLock.enabled) {
73 orientation = Screen.orientation;
74 }
75 }
76 readonly property bool orientationLockEnabled: OrientationLock.enabled
77 onOrientationLockEnabledChanged: {
78 if (orientationLockEnabled) {
79 OrientationLock.savedOrientation = Screen.orientation;
80 } else {
81 orientation = Screen.orientation;
82 }
83 }
85 function activateApplication(appId) {
86 if (ApplicationManager.findApplication(appId)) {
87 ApplicationManager.requestFocusApplication(appId);
88 } else {
89 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
90 ApplicationManager.startApplication(appId, execFlags);
91 }
92 }
94 function startLockedApp(app) {
95 if (shell.locked) {
96 greeter.lockedApp = app;
97 }
98 shell.activateApplication(app);
99 }
101 // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
102 Image {
103 id: asImageTester
104 source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
105 height: 0
106 width: 0
107 sourceSize.height: 0
108 sourceSize.width: 0
109 }
111 GSettings {
112 id: backgroundSettings
113 schema.id: "org.gnome.desktop.background"
114 }
116 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
117 Image {
118 id: gsImageTester
119 source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
120 height: 0
121 width: 0
122 sourceSize.height: 0
123 sourceSize.width: 0
124 }
126 Binding {
127 target: LauncherModel
128 property: "applicationManager"
129 value: ApplicationManager
130 }
132 Component.onCompleted: {
133 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
134 if (ApplicationManager.count > 0) {
135 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
136 }
137 if (orientationLockEnabled) {
138 orientation = OrientationLock.savedOrientation;
139 }
140 Connectivity.unlockAllModems();
141 }
143 VolumeControl {
144 id: volumeControl
145 }
147 DashCommunicator {
148 id: dash
149 objectName: "dashCommunicator"
150 }
152 ScreenGrabber {
153 id: screenGrabber
154 z: edgeDemo.z + 10
155 enabled: Powerd.status === Powerd.On
156 }
158 Binding {
159 target: ApplicationManager
160 property: "forceDashActive"
161 value: launcher.shown || launcher.dashSwipe
162 }
164 VolumeKeyFilter {
165 id: volumeKeyFilter
166 onVolumeDownPressed: volumeControl.volumeDown()
167 onVolumeUpPressed: volumeControl.volumeUp()
168 onBothVolumeKeysPressed: screenGrabber.capture()
169 }
171 WindowKeysFilter {
172 Keys.onPressed: {
173 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
174 dialogs.onPowerKeyPressed();
175 event.accepted = true;
176 } else {
177 volumeKeyFilter.onKeyPressed(event.key);
178 event.accepted = false;
179 }
180 }
182 Keys.onReleased: {
183 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
184 dialogs.onPowerKeyReleased();
185 event.accepted = true;
186 } else {
187 volumeKeyFilter.onKeyReleased(event.key);
188 event.accepted = false;
189 }
190 }
191 }
193 Item {
194 id: stages
195 objectName: "stages"
196 width: parent.width
197 height: parent.height
198 visible: !ApplicationManager.empty
200 Connections {
201 target: ApplicationManager
203 onFocusRequested: {
204 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
205 // If we are in the middle of a call, make dialer lockedApp and show it.
206 // This can happen if user backs out of dialer back to greeter, then
207 // launches dialer again.
208 lockedApp = appId;
209 }
211 greeter.notifyAppFocused(appId);
212 }
214 onFocusedApplicationIdChanged: {
215 greeter.notifyFocusChanged(ApplicationManager.focusedApplicationId);
216 panel.indicators.hide();
217 }
219 onApplicationAdded: {
220 if (edgeDemo.running && appId != "unity8-dash") {
221 // If this happens on first boot, we may be in edge
222 // tutorial or wizard while receiving a call. But a call
223 // is more important than wizard so just bail out of those.
224 edgeDemo.hideEdgeDemos();
225 wizard.hide();
226 }
228 greeter.notifyAppAdded(appId);
229 launcher.hide();
230 }
231 }
233 Loader {
234 id: applicationsDisplayLoader
235 objectName: "applicationsDisplayLoader"
236 anchors.fill: parent
238 // When we have a locked app, we only want to show that one app.
239 // FIXME: do this in a less traumatic way. We currently only allow
240 // locked apps in phone mode (see FIXME in Lockscreen component in
241 // this same file). When that changes, we need to do something
242 // nicer here. But this code is currently just to prevent a
243 // theoretical attack where user enters lockedApp mode, then makes
244 // the screen larger (maybe connects to monitor) and tries to enter
245 // tablet mode.
246 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
247 source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
249 Binding {
250 target: applicationsDisplayLoader.item
251 property: "objectName"
252 value: "stage"
253 }
254 Binding {
255 target: applicationsDisplayLoader.item
256 property: "dragAreaWidth"
257 value: shell.edgeSize
258 }
259 Binding {
260 target: applicationsDisplayLoader.item
261 property: "maximizedAppTopMargin"
262 // Not just using panel.panelHeight as that changes depending on the focused app.
263 value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
264 }
265 Binding {
266 target: applicationsDisplayLoader.item
267 property: "interactive"
268 value: edgeDemo.stagesEnabled && !greeter.fullyShown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
269 }
270 Binding {
271 target: applicationsDisplayLoader.item
272 property: "spreadEnabled"
273 value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
274 }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "inverseProgress"
278 value: launcher.progress
279 }
280 Binding {
281 target: applicationsDisplayLoader.item
282 property: "orientation"
283 value: shell.orientation
284 }
285 }
286 }
288 InputMethod {
289 id: inputMethod
290 objectName: "inputMethod"
291 anchors { fill: parent; topMargin: panel.panelHeight }
292 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
293 }
295 Connections {
296 target: SurfaceManager
297 onSurfaceCreated: {
298 if (surface.type == MirSurfaceItem.InputMethod) {
299 inputMethod.surface = surface;
300 }
301 }
303 onSurfaceDestroyed: {
304 if (inputMethod.surface == surface) {
305 inputMethod.surface = null;
306 surface.parent = null;
307 }
308 if (!surface.parent) {
309 // there's no one displaying it. delete it right away
310 surface.release();
311 }
312 }
313 }
314 Connections {
315 target: SessionManager
316 onSessionStopping: {
317 if (!session.parentSession && !session.application) {
318 // nothing is using it. delete it right away
319 session.release();
320 }
321 }
322 }
324 Greeter {
325 id: greeter
326 objectName: "greeter"
328 hides: [launcher, panel.indicators]
329 tabletMode: shell.sideStageEnabled
330 launcherOffset: narrowMode ? launcher.progress : 0
331 forcedUnlock: edgeDemo.running
332 background: shell.background
334 anchors.fill: parent
335 anchors.topMargin: panel.panelHeight
337 onSessionStarted: {
338 launcher.hide();
339 }
341 onTease: launcher.tease()
342 onEmergencyCall: startLockedApp("dialer-app")
344 Binding {
345 target: ApplicationManager
346 property: "suspended"
347 value: greeter.fullyShown
348 }
349 }
351 Connections {
352 id: callConnection
353 target: callManager
355 onHasCallsChanged: {
356 if (shell.locked && callManager.hasCalls) {
357 // We just received an incoming call while locked. The
358 // indicator will have already launched dialer-app for us, but
359 // there is a race between "hasCalls" changing and the dialer
360 // starting up. So in case we lose that race, we'll start/
361 // focus the dialer ourselves here too. Even if the indicator
362 // didn't launch the dialer for some reason (or maybe a call
363 // started via some other means), if an active call is
364 // happening, we want to be in the dialer.
365 startLockedApp("dialer-app")
366 }
367 }
368 }
370 Connections {
371 id: powerConnection
372 target: Powerd
374 onStatusChanged: {
375 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
376 !callManager.hasCalls && !edgeDemo.running) {
377 greeter.showNow()
378 }
379 }
380 }
382 function showHome() {
383 if (edgeDemo.running) {
384 return
385 }
387 if (LightDM.Greeter.active) {
388 greeter.startUnlock()
389 }
391 var animate = !LightDM.Greeter.active && !stages.shown
392 dash.setCurrentScope(0, animate, false)
393 ApplicationManager.requestFocusApplication("unity8-dash")
394 }
396 function showDash() {
397 if (greeter.hasLockedApp || // just in case user gets here
398 (!greeter.narrowMode && shell.locked)) {
399 return
400 }
402 if (greeter.shown) {
403 greeter.hideRight();
404 launcher.fadeOut();
405 }
407 if (ApplicationManager.focusedApplicationId != "unity8-dash") {
408 ApplicationManager.requestFocusApplication("unity8-dash")
409 launcher.fadeOut();
410 }
411 }
413 Item {
414 id: overlay
415 z: 10
417 anchors.fill: parent
419 Panel {
420 id: panel
421 objectName: "panel"
422 anchors.fill: parent //because this draws indicator menus
423 indicators {
424 hides: [launcher]
425 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
426 contentEnabled: edgeDemo.panelContentEnabled
427 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
429 minimizedPanelHeight: units.gu(3)
430 expandedPanelHeight: units.gu(7)
432 indicatorsModel: visibleIndicators.model
433 }
435 VisibleIndicators {
436 id: visibleIndicators
437 // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
438 Component.onCompleted: initialise(indicatorProfile)
439 }
440 callHint {
441 greeterShown: greeter.fullyShown
442 }
444 property bool topmostApplicationIsFullscreen:
445 ApplicationManager.focusedApplicationId &&
446 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
448 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
449 || greeter.hasLockedApp
450 }
452 Launcher {
453 id: launcher
454 objectName: "launcher"
456 readonly property bool dashSwipe: progress > 0
458 anchors.top: parent.top
459 anchors.bottom: parent.bottom
460 width: parent.width
461 dragAreaWidth: shell.edgeSize
462 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
464 onShowDashHome: showHome()
465 onDash: showDash()
466 onDashSwipeChanged: {
467 if (dashSwipe) {
468 dash.setCurrentScope(0, false, true)
469 }
470 }
471 onLauncherApplicationSelected: {
472 if (!edgeDemo.running) {
473 greeter.notifyAboutToStartApp(appId);
474 shell.activateApplication(appId)
475 }
476 }
477 onShownChanged: {
478 if (shown) {
479 panel.indicators.hide()
480 }
481 }
482 }
484 Wizard {
485 id: wizard
486 anchors.fill: parent
487 background: shell.background
488 }
490 Rectangle {
491 id: modalNotificationBackground
493 visible: notifications.useModal && (notifications.state == "narrow")
494 color: "#000000"
495 anchors.fill: parent
496 opacity: 0.9
498 MouseArea {
499 anchors.fill: parent
500 }
501 }
503 Notifications {
504 id: notifications
506 model: NotificationBackend.Model
507 margin: units.gu(1)
509 y: topmostIsFullscreen ? 0 : panel.panelHeight
510 width: parent.width
511 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
513 states: [
514 State {
515 name: "narrow"
516 when: overlay.width <= units.gu(60)
517 AnchorChanges { target: notifications; anchors.left: parent.left }
518 },
519 State {
520 name: "wide"
521 when: overlay.width > units.gu(60)
522 AnchorChanges { target: notifications; anchors.left: undefined }
523 PropertyChanges { target: notifications; width: units.gu(38) }
524 }
525 ]
526 }
527 }
529 Dialogs {
530 id: dialogs
531 anchors.fill: parent
532 z: overlay.z + 10
533 onPowerOffClicked: {
534 shutdownFadeOutRectangle.enabled = true;
535 shutdownFadeOutRectangle.visible = true;
536 shutdownFadeOut.start();
537 }
538 }
540 EdgeDemo {
541 id: edgeDemo
542 objectName: "edgeDemo"
543 z: dialogs.z + 10
544 paused: Powerd.status === Powerd.Off || wizard.active // Saves power
545 greeter: greeter
546 launcher: launcher
547 panel: panel
548 stages: stages
549 }
551 Connections {
552 target: SessionBroadcast
553 onShowHome: showHome()
554 }
556 Rectangle {
557 id: shutdownFadeOutRectangle
558 z: edgeDemo.z + 10
559 enabled: false
560 visible: false
561 color: "black"
562 anchors.fill: parent
563 opacity: 0.0
564 NumberAnimation on opacity {
565 id: shutdownFadeOut
566 from: 0.0
567 to: 1.0
568 onStopped: {
569 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
570 DBusUnitySessionService.Shutdown();
571 }
572 }
573 }
574 }