Add support for handling external sink/source volume change events 55/9155/3
authorMatt Porter <mporter@konsulko.com>
Thu, 20 Apr 2017 16:49:15 +0000 (12:49 -0400)
committerMatt Porter <mporter@konsulko.com>
Fri, 21 Apr 2017 16:50:02 +0000 (12:50 -0400)
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 <mporter@konsulko.com>
app/Mixer.qml
app/main.cpp
app/paclient.cpp
app/paclient.h
app/pacontrolmodel.cpp
app/pacontrolmodel.h

index 75673fe..58124c7 100644 (file)
@@ -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
index 76e755b..1868ac6 100644 (file)
@@ -67,6 +67,8 @@ int main(int argc, char *argv[])
        PaControlModel *pacm = mobjs.first()->findChild<PaControlModel *>("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)));
 
index 58d8fad..afe0fad 100644 (file)
@@ -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<PaClient*>(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<PaClient*>(data);
+               QHash<int, pa_cvolume *> 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<PaClient*>(data);
+               QHash<int, pa_cvolume *> 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<int, pa_cvolume *> PaClient::sink_states(void)
+{
+       return m_sink_states;
+}
+
+QHash<int, pa_cvolume *> PaClient::source_states(void)
+{
+       return m_source_states;
+}
index 002fbb3..b8a7961 100644 (file)
@@ -75,11 +75,15 @@ class PaClient : public QObject
 
                void addOneControlState(int type, int index, const pa_cvolume *cvolume);
 
+               QHash<int, pa_cvolume *> sink_states();
+               QHash<int, pa_cvolume *> 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<int, pa_cvolume *> m_sink_states;
                QHash<int, pa_cvolume *> m_source_states;
+
+       public slots:
 };
index bca72c5..fe5de53 100644 (file)
@@ -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<PaControl>::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<int> 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<int> roles;
+       roles.push_back(role);
+       emit dataChanged(index, index, roles);
        return true;
 }
 
index 475f7ce..a3cd5ae 100644 (file)
@@ -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);