summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Porter <mporter@konsulko.com>2017-04-20 12:49:15 -0400
committerMatt Porter <mporter@konsulko.com>2017-04-21 12:50:02 -0400
commit288256f33f6298204cd0166cea3202d1fde100da (patch)
tree2aa30a1b09c99e63ce3868ce2fa925472b70bf7c
parent9e23981d8a86a3530c03be2d541585ce88e7b914 (diff)
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 <mporter@konsulko.com>
-rw-r--r--app/Mixer.qml5
-rw-r--r--app/main.cpp2
-rw-r--r--app/paclient.cpp115
-rw-r--r--app/paclient.h6
-rw-r--r--app/pacontrolmodel.cpp27
-rw-r--r--app/pacontrolmodel.h1
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<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)));
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<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;
+}
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<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:
};
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<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;
}
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);