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