diff options
-rw-r--r-- | homescreen/homescreen.pro | 11 | ||||
-rw-r--r-- | homescreen/qml/MediaAreaBlank.qml | 106 | ||||
-rw-r--r-- | homescreen/src/main.cpp | 19 | ||||
-rw-r--r-- | homescreen/src/mastervolume.cpp | 33 | ||||
-rw-r--r-- | homescreen/src/mastervolume.h | 49 | ||||
-rw-r--r-- | homescreen/src/paclient.cpp | 240 | ||||
-rw-r--r-- | homescreen/src/paclient.h | 64 |
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; +}; |