aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--homescreen/homescreen.pro11
-rw-r--r--homescreen/qml/MediaAreaBlank.qml106
-rw-r--r--homescreen/src/main.cpp19
-rw-r--r--homescreen/src/mastervolume.cpp33
-rw-r--r--homescreen/src/mastervolume.h49
-rw-r--r--homescreen/src/paclient.cpp240
-rw-r--r--homescreen/src/paclient.h64
7 files changed, 519 insertions, 3 deletions
diff --git a/homescreen/homescreen.pro b/homescreen/homescreen.pro
index 1ca59b6..061d2fb 100644
--- a/homescreen/homescreen.pro
+++ b/homescreen/homescreen.pro
@@ -15,7 +15,8 @@
TEMPLATE = app
TARGET = HomeScreen
QT = qml quick dbus
-CONFIG += c++11
+CONFIG += c++11 link_pkgconfig
+PKGCONFIG += libpulse
include(../interfaces/interfaces.pri)
@@ -27,7 +28,9 @@ SOURCES += \
src/appinfo.cpp \
src/statusbarmodel.cpp \
src/statusbarserver.cpp \
- src/applicationlauncher.cpp
+ src/applicationlauncher.cpp \
+ src/mastervolume.cpp \
+ src/paclient.cpp
HEADERS += \
src/homescreencontrolinterface.h \
@@ -36,7 +39,9 @@ HEADERS += \
src/statusbarserver.h \
src/applicationlauncher.h \
src/applicationmodel.h \
- src/appinfo.h
+ src/appinfo.h \
+ src/mastervolume.h \
+ src/paclient.h
OTHER_FILES += \
README.md
diff --git a/homescreen/qml/MediaAreaBlank.qml b/homescreen/qml/MediaAreaBlank.qml
index 0dde451..51fa657 100644
--- a/homescreen/qml/MediaAreaBlank.qml
+++ b/homescreen/qml/MediaAreaBlank.qml
@@ -16,14 +16,120 @@
*/
import QtQuick 2.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.0
+import AGL.Demo.Controls 1.0
+import MasterVolume 1.0
Image {
width: 1080
height: 215
source: './images/Utility_Logo_Background-01.png'
+ property bool displayVolume: false;
+
+ MouseArea {
+ anchors.fill: parent
+ function enableVolumeDisplay() {
+ if (!displayVolume) {
+ displayVolume = true
+ master_volume.visible = true
+ volume_timer.restart()
+ }
+ }
+ onClicked: enableVolumeDisplay()
+ }
Image {
+ id: logo_image
anchors.centerIn: parent
source: './images/Utility_Logo_Colour-01.png'
}
+
+ Timer {
+ id: volume_timer
+ interval: 5000; running: false; repeat: false
+ onTriggered: displayVolume = false
+ }
+
+ states: [
+ State { when: displayVolume;
+ PropertyChanges { target: master_volume; opacity: 1.0 }
+ PropertyChanges { target: slider; enabled: true }
+ PropertyChanges { target: logo_image; opacity: 0.0 }
+ },
+ State { when: !displayVolume;
+ PropertyChanges { target: master_volume; opacity: 0.0 }
+ PropertyChanges { target: slider; enabled: false }
+ PropertyChanges { target: logo_image; opacity: 1.0 }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation { property: "opacity"; duration: 500}
+ }
+
+ MasterVolume {
+ id: mv
+ objectName: "mv"
+ onVolumeChanged: slider.value = volume
+ }
+
+ Item {
+ id: master_volume
+ anchors.fill: parent
+ anchors.centerIn: parent
+ visible: false
+
+ Label {
+ font.pixelSize: 36
+ anchors.horizontalCenter: parent.horizontalCenter
+ color: "white"
+ text: "Master Volume"
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.centerIn: parent
+ anchors.margins: 20
+ spacing: 20
+ Label {
+ font.pixelSize: 36
+ color: "white"
+ text: "0 %"
+ }
+ Slider {
+ id: slider
+ Layout.fillWidth: true
+ from: 0
+ to: 65536
+ stepSize: 256
+ snapMode: Slider.SnapOnRelease
+ onValueChanged: mv.volume = value
+ Component.onCompleted: value = mv.volume
+ onPressedChanged: {
+ if (pressed) {volume_timer.stop()}
+ else {volume_timer.restart()}
+ }
+ background: Rectangle {
+ id: slider_bg
+ height: 16
+ color: "#59FF7F"
+ }
+ handle: Rectangle {
+ anchors.verticalCenter: slider_bg.verticalCenter
+ width: 48
+ height: 48
+ radius: 24
+ x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
+ y: slider.topPadding + slider.availableHeight / 2 - height / 2
+ color: "white"
+ }
+ }
+ Label {
+ font.pixelSize: 36
+ color: "white"
+ text: "100 %"
+ }
+ }
+ }
}
diff --git a/homescreen/src/main.cpp b/homescreen/src/main.cpp
index 28a8d38..215e7c6 100644
--- a/homescreen/src/main.cpp
+++ b/homescreen/src/main.cpp
@@ -28,6 +28,8 @@
#include "applicationmodel.h"
#include "appinfo.h"
#include "afm_user_daemon_proxy.h"
+#include "mastervolume.h"
+#include "paclient.h"
// XXX: We want this DBus connection to be shared across the different
// QML objects, is there another way to do this, a nice way, perhaps?
@@ -77,18 +79,26 @@ int main(int argc, char *argv[])
qInstallMessageHandler(noOutput);
}
+ // Fire up PA client QThread
+ QThread* pat = new QThread;
+ PaClient* client = new PaClient();
+ client->moveToThread(pat);
+ pat->start();
+
qDBusRegisterMetaType<AppInfo>();
qDBusRegisterMetaType<QList<AppInfo> >();
qmlRegisterType<ApplicationLauncher>("HomeScreen", 1, 0, "ApplicationLauncher");
qmlRegisterType<ApplicationModel>("Home", 1, 0, "ApplicationModel");
qmlRegisterType<StatusBarModel>("HomeScreen", 1, 0, "StatusBarModel");
+ qmlRegisterType<MasterVolume>("MasterVolume", 1, 0, "MasterVolume");
QQmlApplicationEngine engine;
LayoutHandler* layoutHandler = new LayoutHandler();
HomeScreenControlInterface* hsci = new HomeScreenControlInterface();
+
QObject::connect(hsci, SIGNAL(newRequestGetSurfaceStatus(int)), layoutHandler, SLOT(requestGetSurfaceStatus(int)));
QObject::connect(hsci, SIGNAL(newRequestsToBeVisibleApp(int)), layoutHandler, SLOT(makeMeVisible(int)));
QObject::connect(hsci, SIGNAL(newRequestRenderSurfaceToArea(int, int)), layoutHandler, SLOT(requestRenderSurfaceToArea(int,int)));
@@ -99,5 +109,14 @@ int main(int argc, char *argv[])
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+ QList<QObject *> mobjs = engine.rootObjects();
+ MasterVolume *mv = mobjs.first()->findChild<MasterVolume *>("mv");
+ engine.rootContext()->setContextProperty("MasterVolume", mv);
+ QObject::connect(mv, SIGNAL(sliderVolumeChanged(int)), client, SLOT(incDecVolume(int)));
+ QObject::connect(client, SIGNAL(volumeExternallyChanged(int)), mv, SLOT(changeExternalVolume(int)));
+
+ // Initalize PA client
+ client->init();
+
return a.exec();
}
diff --git a/homescreen/src/mastervolume.cpp b/homescreen/src/mastervolume.cpp
new file mode 100644
index 0000000..46985ee
--- /dev/null
+++ b/homescreen/src/mastervolume.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mastervolume.h"
+
+#include <QtCore/QDebug>
+
+void MasterVolume::setVolume(pa_volume_t volume)
+{
+ int volume_delta = volume - m_volume;
+ m_volume = volume;
+ emit sliderVolumeChanged(volume_delta);
+
+}
+
+void MasterVolume::changeExternalVolume(int volume)
+{
+ m_volume = volume;
+ emit volumeChanged();
+}
diff --git a/homescreen/src/mastervolume.h b/homescreen/src/mastervolume.h
new file mode 100644
index 0000000..edcdd8c
--- /dev/null
+++ b/homescreen/src/mastervolume.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QtCore/QObject>
+#include <QQmlEngine>
+#include <QDebug>
+
+#include <pulse/pulseaudio.h>
+
+class MasterVolume : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY (uint32_t volume READ getVolume WRITE setVolume NOTIFY volumeChanged)
+
+ public:
+ MasterVolume(QObject *parent = 0)
+ : QObject(parent), m_volume(32768)
+ {
+ }
+
+ ~MasterVolume() {}
+
+ uint32_t getVolume() const { return m_volume; }
+ void setVolume(pa_volume_t volume);
+
+ public slots:
+ void changeExternalVolume(int volume);
+
+ signals:
+ void volumeChanged(void);
+ void sliderVolumeChanged(int volume_delta);
+ void externalVolumeChanged(uint32_t volume);
+
+ private:
+ uint32_t m_volume;
+};
diff --git a/homescreen/src/paclient.cpp b/homescreen/src/paclient.cpp
new file mode 100644
index 0000000..59a3742
--- /dev/null
+++ b/homescreen/src/paclient.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016,2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "paclient.h"
+
+#include <QtCore/QDebug>
+
+PaClient::PaClient()
+ : m_init(false), m_ml(nullptr), m_mlapi(nullptr), m_ctx(nullptr)
+{
+}
+
+PaClient::~PaClient()
+{
+ if (m_init)
+ close();
+}
+
+void PaClient::close()
+{
+ if (!m_init) return;
+ pa_threaded_mainloop_stop(m_ml);
+ pa_threaded_mainloop_free(m_ml);
+ m_init = false;
+}
+
+static void set_sink_volume_cb(pa_context *c, int success, void *data)
+{
+ Q_UNUSED(data);
+
+ if (!success)
+ qWarning() << "PaClient: set sink volume: " <<
+ pa_strerror(pa_context_errno(c));
+}
+
+void PaClient::incDecVolume(const int volume_delta)
+{
+ pa_operation *o;
+ pa_context *c = context();
+ pa_sink_info *i = getDefaultSinkInfo();
+
+ if (volume_delta > 0)
+ pa_cvolume_inc_clamp(&i->volume, volume_delta, 65536);
+ else
+ pa_cvolume_dec(&i->volume, abs(volume_delta));
+
+ o = pa_context_set_sink_volume_by_index(c, i->index, &i->volume, set_sink_volume_cb, NULL);
+ if (!o) {
+ qWarning() << "PaClient: set sink #" << i->index <<
+ " volume: " << pa_strerror(pa_context_errno(c));
+ return;
+ }
+ pa_operation_unref(o);
+}
+
+void get_sink_info_change_cb(pa_context *c,
+ const pa_sink_info *i,
+ int eol,
+ void *data)
+{
+ Q_UNUSED(c);
+ Q_ASSERT(i);
+ Q_ASSERT(data);
+
+ if (eol) return;
+
+ PaClient *self = reinterpret_cast<PaClient*>(data);
+ pa_sink_info *si = self->getDefaultSinkInfo();
+ if (i->index == si->index) {
+ self->setDefaultSinkInfo(i);
+ pa_cvolume *cvolume = &self->getDefaultSinkInfo()->volume;
+ emit self->volumeExternallyChanged(pa_cvolume_avg(cvolume));
+ }
+}
+
+void get_default_sink_info_cb(pa_context *c,
+ const pa_sink_info *i,
+ int eol,
+ void *data)
+{
+ Q_UNUSED(c);
+ Q_ASSERT(i);
+ Q_ASSERT(data);
+
+ if (eol) return;
+
+ PaClient *self = reinterpret_cast<PaClient*>(data);
+ self->setDefaultSinkInfo(i);
+}
+
+pa_sink_info *PaClient::getDefaultSinkInfo(void)
+{
+ return &m_default_sink_info;
+}
+
+void PaClient::setDefaultSinkInfo(const pa_sink_info *i)
+{
+ m_default_sink_info.index = i->index;
+ m_default_sink_info.channel_map.channels = i->channel_map.channels;
+ pa_cvolume *cvolume = &m_default_sink_info.volume;
+ cvolume->channels = i->volume.channels;
+ for (int chan = 0; chan < i->channel_map.channels; chan++) {
+ cvolume->values[chan] = i->volume.values[chan];
+ }
+}
+
+void get_server_info_cb(pa_context *c,
+ const pa_server_info *i,
+ void *data)
+{
+ pa_operation *o;
+ o = pa_context_get_sink_info_by_name(c, i->default_sink_name, get_default_sink_info_cb, data);
+ if (!o) {
+ qWarning() << "PaClient: get sink info by name: " <<
+ pa_strerror(pa_context_errno(c));
+ return;
+ }
+}
+
+void subscribe_cb(pa_context *c,
+ pa_subscription_event_type_t type,
+ uint32_t index,
+ void *data)
+{
+ pa_operation *o;
+
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {
+ qWarning("PaClient: unhandled subscribe event operation");
+ return;
+ }
+
+ switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ o = pa_context_get_sink_info_by_index(c, index, get_sink_info_change_cb, data);
+ if (!o) {
+ qWarning() << "PaClient: get sink info by index: " <<
+ pa_strerror(pa_context_errno(c));
+ return;
+ }
+ break;
+ default:
+ qWarning("PaClient: unhandled subscribe event facility");
+ }
+}
+
+void context_state_cb(pa_context *c, void *data)
+{
+ pa_operation *o;
+ PaClient *self = reinterpret_cast<PaClient*>(data);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ o = pa_context_get_server_info(c, get_server_info_cb, data);
+ if (!o) {
+ qWarning() << "PaClient: get server info: " <<
+ pa_strerror(pa_context_errno(c));
+ return;
+ }
+ pa_operation_unref(o);
+ pa_context_set_subscribe_callback(c, subscribe_cb, data);
+ o = pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK), NULL, NULL);
+ if (!o) {
+ qWarning() << "PaClient: subscribe: " <<
+ pa_strerror(pa_context_errno(c));
+ return;
+ }
+ break;
+ case PA_CONTEXT_TERMINATED:
+ self->close();
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ qCritical() << "PaClient: connection failed: " <<
+ pa_strerror(pa_context_errno(c));
+ self->close();
+ break;
+ }
+}
+
+void PaClient::init()
+{
+ m_ml = pa_threaded_mainloop_new();
+ if (!m_ml) {
+ qCritical("PaClient: failed to create mainloop");
+ return;
+ }
+
+ pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
+
+ m_mlapi = pa_threaded_mainloop_get_api(m_ml);
+
+ lock();
+
+ m_ctx = pa_context_new(m_mlapi, "HomeScreen");
+ if (!m_ctx) {
+ qCritical("PaClient: failed to create context");
+ unlock();
+ pa_threaded_mainloop_free(m_ml);
+ return;
+ }
+ pa_context_set_state_callback(m_ctx, context_state_cb, this);
+
+ if (pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) {
+ qCritical("PaClient: failed to connect");
+ pa_context_unref(m_ctx);
+ unlock();
+ pa_threaded_mainloop_free(m_ml);
+ return;
+ }
+
+ if (pa_threaded_mainloop_start(m_ml) != 0) {
+ qCritical("PaClient: failed to start mainloop");
+ pa_context_unref(m_ctx);
+ unlock();
+ pa_threaded_mainloop_free(m_ml);
+ return;
+ }
+
+ unlock();
+
+ m_init = true;
+}
diff --git a/homescreen/src/paclient.h b/homescreen/src/paclient.h
new file mode 100644
index 0000000..abf178b
--- /dev/null
+++ b/homescreen/src/paclient.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016,2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pulse/pulseaudio.h>
+
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+
+class PaClient : public QObject
+{
+ Q_OBJECT
+ public:
+ PaClient();
+ ~PaClient();
+
+ void init();
+ void close();
+
+ inline pa_context *context() const
+ {
+ return m_ctx;
+ }
+
+ inline void lock()
+ {
+ pa_threaded_mainloop_lock(m_ml);
+ }
+
+ inline void unlock()
+ {
+ pa_threaded_mainloop_unlock(m_ml);
+ }
+
+ pa_sink_info * getDefaultSinkInfo(void);
+ void setDefaultSinkInfo(const pa_sink_info *i);
+ void setMasterVolume(const pa_cvolume *);
+
+ public slots:
+ void incDecVolume(const int volume_delta);
+
+ signals:
+ void volumeExternallyChanged(int volume);
+
+ private:
+ bool m_init;
+ pa_threaded_mainloop *m_ml;
+ pa_mainloop_api *m_mlapi;
+ pa_context *m_ctx;
+ pa_cvolume m_master_cvolume;
+ pa_sink_info m_default_sink_info;
+};