From 288256f33f6298204cd0166cea3202d1fde100da Mon Sep 17 00:00:00 2001 From: Matt Porter Date: Thu, 20 Apr 2017 12:49:15 -0400 Subject: Add support for handling external sink/source volume change events Subscribes to PA volume change events, updating the local cached volume levels, and propagating the change to the UI. This allows changes to sink/source volumes levels from the command line (pactl) or a master volume control to be reflected in the mixer UI controls. Change-Id: I1d570dffeab9fcf4b6ba51e4792852b44a6149ca AGL-Bug: SPEC-549 Signed-off-by: Matt Porter --- app/Mixer.qml | 5 +++ app/main.cpp | 2 + app/paclient.cpp | 115 +++++++++++++++++++++++++++++++++++++++++++++---- app/paclient.h | 6 +++ app/pacontrolmodel.cpp | 27 +++++++++++- app/pacontrolmodel.h | 1 + 6 files changed, 146 insertions(+), 10 deletions(-) diff --git a/app/Mixer.qml b/app/Mixer.qml index 75673fe..58124c7 100644 --- a/app/Mixer.qml +++ b/app/Mixer.qml @@ -57,6 +57,10 @@ ApplicationWindow { delegate: ColumnLayout { width: parent.width spacing: 40 + Connections { + target: listView.model + onDataChanged: slider.value = volume + } Loader { property int modelType: type property int modelCIndex: cindex @@ -75,6 +79,7 @@ ApplicationWindow { text: "0 %" } Slider { + id: slider Layout.fillWidth: true from: 0 to: 65536 diff --git a/app/main.cpp b/app/main.cpp index 76e755b..1868ac6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -67,6 +67,8 @@ int main(int argc, char *argv[]) PaControlModel *pacm = mobjs.first()->findChild("pacm"); QObject::connect(client, SIGNAL(controlAdded(int, QString, int, int, const char *, int)), pacm, SLOT(addOneControl(int, QString, int, int, const char *, int))); + QObject::connect(client, SIGNAL(volumeExternallyChanged(uint32_t, uint32_t, uint32_t, uint32_t)), + pacm, SLOT(changeExternalVolume(uint32_t, uint32_t, uint32_t, uint32_t))); QObject::connect(pacm, SIGNAL(volumeChanged(uint32_t, uint32_t, uint32_t, uint32_t)), client, SLOT(setVolume(uint32_t, uint32_t, uint32_t, uint32_t))); diff --git a/app/paclient.cpp b/app/paclient.cpp index 58d8fad..afe0fad 100644 --- a/app/paclient.cpp +++ b/app/paclient.cpp @@ -37,15 +37,19 @@ void PaClient::close() m_init = false; } -static void set_sink_volume_cb(pa_context *c, int success, void *data __attribute__((unused))) +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)); } -static void set_source_volume_cb(pa_context *c, int success, void *data __attribute__((unused))) +static void set_source_volume_cb(pa_context *c, int success, void *data) { + Q_UNUSED(data); + if (!success) qWarning() << "PaClient: set source volume: " << pa_strerror(pa_context_errno(c)); @@ -112,8 +116,8 @@ void get_sink_list_cb(pa_context *c, int eol, void *data) { - int chan; PaClient *self = reinterpret_cast(data); + int chan; if(eol < 0) { qWarning() << "PaClient: get sink list: " << @@ -132,6 +136,87 @@ void get_sink_list_cb(pa_context *c, } } +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; + + for (int chan = 0; chan < i->channel_map.channels; chan++) { + PaClient *self = reinterpret_cast(data); + QHash states = self->sink_states(); + pa_cvolume *cvolume = states.value(i->index); + // Check each channel for volume change + if (cvolume->values[chan] != i->volume.values[chan]) { + // On change, update cache and signal + cvolume->values[chan] = i->volume.values[chan]; + emit self->volumeExternallyChanged(C_SINK, i->index, chan, i->volume.values[chan]); + } + } +} + +void get_source_info_change_cb(pa_context *c, + const pa_source_info *i, + int eol, + void *data) +{ + Q_UNUSED(c); + Q_ASSERT(i); + Q_ASSERT(data); + + if (eol) return; + + for (int chan = 0; chan < i->channel_map.channels; chan++) { + PaClient *self = reinterpret_cast(data); + QHash states = self->source_states(); + pa_cvolume *cvolume = states.value(i->index); + // Check each channel for volume change + if (cvolume->values[chan] != i->volume.values[chan]) { + // On change, update cache and signal + cvolume->values[chan] = i->volume.values[chan]; + emit self->volumeExternallyChanged(C_SOURCE, i->index, chan, i->volume.values[chan]); + } + } +} + + +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: + if (!(o = pa_context_get_sink_info_by_index(c, index, get_sink_info_change_cb, data))) { + qWarning() << "PaClient: get sink info by index: " << + pa_strerror(pa_context_errno(c)); + return; + } + break; + case PA_SUBSCRIPTION_EVENT_SOURCE: + if (!(o = pa_context_get_source_info_by_index(c, index, get_source_info_change_cb, data))) { + qWarning() << "PaClient: get source 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; @@ -155,6 +240,13 @@ void context_state_cb(pa_context *c, void *data) pa_strerror(pa_context_errno(c)); return; } + pa_operation_unref(o); + pa_context_set_subscribe_callback(c, subscribe_cb, data); + if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE), NULL, NULL))) { + qWarning() << "PaClient: subscribe: " << + pa_strerror(pa_context_errno(c)); + return; + } break; case PA_CONTEXT_TERMINATED: self->close(); @@ -212,15 +304,22 @@ void PaClient::init() void PaClient::addOneControlState(int type, int index, const pa_cvolume *cvolume) { - int i; - pa_cvolume *cvolume_new; - - cvolume_new = new pa_cvolume; + pa_cvolume *cvolume_new = new pa_cvolume; cvolume_new->channels = cvolume->channels; - for (i = 0; i < cvolume->channels; i++) + for (int i = 0; i < cvolume->channels; i++) cvolume_new->values[i] = cvolume->values[i]; if (type == C_SINK) m_sink_states.insert(index, cvolume_new); else if (type == C_SOURCE) m_source_states.insert(index, cvolume_new); } + +QHash PaClient::sink_states(void) +{ + return m_sink_states; +} + +QHash PaClient::source_states(void) +{ + return m_source_states; +} diff --git a/app/paclient.h b/app/paclient.h index 002fbb3..b8a7961 100644 --- a/app/paclient.h +++ b/app/paclient.h @@ -75,11 +75,15 @@ class PaClient : public QObject void addOneControlState(int type, int index, const pa_cvolume *cvolume); + QHash sink_states(); + QHash source_states(); + public slots: void setVolume(uint32_t type, uint32_t index, uint32_t channel, uint32_t volume); signals: void controlAdded(int cindex, QString desc, int type, int channel, const char *cdesc, int volume); + void volumeExternallyChanged(uint32_t type, uint32_t cindex, uint32_t channel, uint32_t volume); private: bool m_init; @@ -88,4 +92,6 @@ class PaClient : public QObject pa_context *m_ctx; QHash m_sink_states; QHash m_source_states; + + public slots: }; diff --git a/app/pacontrolmodel.cpp b/app/pacontrolmodel.cpp index bca72c5..fe5de53 100644 --- a/app/pacontrolmodel.cpp +++ b/app/pacontrolmodel.cpp @@ -82,7 +82,8 @@ void PaControl::setVolume(PaControlModel *pacm, const QVariant &volume) { if (volume != m_volume) { m_volume = volume.toUInt(); - emit pacm->volumeChanged(type(), cindex(), channel(), m_volume); + if (pacm) + emit pacm->volumeChanged(type(), cindex(), channel(), m_volume); } } @@ -103,6 +104,26 @@ void PaControlModel::addOneControl(int cindex, QString desc, int type, int chann addControl(PaControl(cindex, desc, type, channel, cdesc, volume)); } +void PaControlModel::changeExternalVolume(uint32_t type, uint32_t cindex, uint32_t channel, uint32_t volume) +{ + QList::iterator i; + int row; + + for (i = m_controls.begin(), row = 0; i < m_controls.end(); ++i, ++row) { + if ((i->type() == type) && + (i->cindex() == cindex) && + (i->channel() == channel)) { + break; + } + } + + i->setVolume(NULL, QVariant(volume)); + QModelIndex qmindex = index(row); + QVector roles; + roles.push_back(VolumeRole); + emit dataChanged(qmindex, qmindex, roles); +} + int PaControlModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); return m_controls.count(); @@ -124,7 +145,9 @@ bool PaControlModel::setData(const QModelIndex &index, const QVariant &value, in control.setCDesc(value); else if (role == VolumeRole) control.setVolume(this, value); - emit dataChanged(index, index); + QVector roles; + roles.push_back(role); + emit dataChanged(index, index, roles); return true; } diff --git a/app/pacontrolmodel.h b/app/pacontrolmodel.h index 475f7ce..a3cd5ae 100644 --- a/app/pacontrolmodel.h +++ b/app/pacontrolmodel.h @@ -75,6 +75,7 @@ class PaControlModel : public QAbstractListModel public slots: void addOneControl(int cindex, QString desc, int type, int channel, const char *cdesc, int volume); + void changeExternalVolume(uint32_t type, uint32_t cindex, uint32_t chan, uint32_t volume); signals: void volumeChanged(uint32_t type, uint32_t index, uint32_t channel, uint32_t volume); -- cgit 1.2.3-korg