2 * Copyright (C) 2016,2017 Konsulko Group
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <QtCore/QDebug>
22 : m_init(false), m_ml(nullptr), m_mlapi(nullptr), m_ctx(nullptr)
32 void PaClient::close()
35 pa_threaded_mainloop_stop(m_ml);
36 pa_threaded_mainloop_free(m_ml);
40 static void set_sink_volume_cb(pa_context *c, int success, void *data)
45 qWarning() << "PaClient: set sink volume: " <<
46 pa_strerror(pa_context_errno(c));
49 static void set_source_volume_cb(pa_context *c, int success, void *data)
54 qWarning() << "PaClient: set source volume: " <<
55 pa_strerror(pa_context_errno(c));
58 void PaClient::setVolume(uint32_t type, uint32_t index, uint32_t channel, uint32_t volume)
61 pa_context *c = context();
62 pa_cvolume *cvolume = NULL;
65 cvolume = m_sink_states.value(index);
66 cvolume->values[channel] = volume;
67 if (!(o = pa_context_set_sink_volume_by_index(c, index, cvolume, set_sink_volume_cb, NULL))) {
68 qWarning() << "PaClient: set sink #" << index <<
69 " channel #" << channel <<
70 " volume: " << pa_strerror(pa_context_errno(c));
73 pa_operation_unref(o);
74 } else if (type == C_SOURCE) {
75 cvolume = m_source_states.value(index);
76 cvolume->values[channel] = volume;
77 if (!(o = pa_context_set_source_volume_by_index(c, index, cvolume, set_source_volume_cb, NULL))) {
78 qWarning() << "PaClient: set source #" << index <<
79 " channel #" << channel <<
80 " volume: " << pa_strerror(pa_context_errno(c));
83 pa_operation_unref(o);
87 void get_source_list_cb(pa_context *c,
88 const pa_source_info *i,
94 PaClient *self = reinterpret_cast<PaClient*>(data);
97 qWarning() << "PaClient: get source list: " <<
98 pa_strerror(pa_context_errno(c));
105 self->addOneControlState(C_SOURCE, i->index, &i->volume);
106 for (chan = 0; chan < i->channel_map.channels; chan++) {
107 // NOTE: hide input control
108 if (QString(i->name).endsWith("monitor"))
111 emit self->controlAdded(i->index, QString(i->name), QString(i->description),
112 C_SOURCE, chan, channel_position_string[i->channel_map.map[chan]],
113 i->volume.values[chan]);
118 void get_sink_list_cb(pa_context *c,
119 const pa_sink_info *i,
123 PaClient *self = reinterpret_cast<PaClient*>(data);
127 qWarning() << "PaClient: get sink list: " <<
128 pa_strerror(pa_context_errno(c));
134 self->addOneControlState(C_SINK, i->index, &i->volume);
135 for (chan = 0; chan < i->channel_map.channels; chan++) {
136 emit self->controlAdded(i->index, QString(i->name), QString(i->description),
137 C_SINK, chan, channel_position_string[i->channel_map.map[chan]],
138 i->volume.values[chan]);
143 void get_sink_info_change_cb(pa_context *c,
144 const pa_sink_info *i,
154 for (int chan = 0; chan < i->channel_map.channels; chan++) {
155 PaClient *self = reinterpret_cast<PaClient*>(data);
156 QHash<int, pa_cvolume *> states = self->sink_states();
157 pa_cvolume *cvolume = states.value(i->index);
158 // Check each channel for volume change
159 if (cvolume->values[chan] != i->volume.values[chan]) {
160 // On change, update cache and signal
161 cvolume->values[chan] = i->volume.values[chan];
162 emit self->volumeExternallyChanged(C_SINK, i->index, chan, i->volume.values[chan]);
167 void get_source_info_change_cb(pa_context *c,
168 const pa_source_info *i,
178 for (int chan = 0; chan < i->channel_map.channels; chan++) {
179 PaClient *self = reinterpret_cast<PaClient*>(data);
180 QHash<int, pa_cvolume *> states = self->source_states();
181 pa_cvolume *cvolume = states.value(i->index);
182 // Check each channel for volume change
183 if (cvolume->values[chan] != i->volume.values[chan]) {
184 // On change, update cache and signal
185 cvolume->values[chan] = i->volume.values[chan];
186 emit self->volumeExternallyChanged(C_SOURCE, i->index, chan, i->volume.values[chan]);
192 void subscribe_cb(pa_context *c,
193 pa_subscription_event_type_t type,
199 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {
200 qWarning("PaClient: unhandled subscribe event operation");
204 switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
205 case PA_SUBSCRIPTION_EVENT_SINK:
206 if (!(o = pa_context_get_sink_info_by_index(c, index, get_sink_info_change_cb, data))) {
207 qWarning() << "PaClient: get sink info by index: " <<
208 pa_strerror(pa_context_errno(c));
212 case PA_SUBSCRIPTION_EVENT_SOURCE:
213 if (!(o = pa_context_get_source_info_by_index(c, index, get_source_info_change_cb, data))) {
214 qWarning() << "PaClient: get source info by index: " <<
215 pa_strerror(pa_context_errno(c));
220 qWarning("PaClient: unhandled subscribe event facility");
224 void context_state_cb(pa_context *c, void *data)
227 PaClient *self = reinterpret_cast<PaClient*>(data);
229 switch (pa_context_get_state(c)) {
230 case PA_CONTEXT_CONNECTING:
231 case PA_CONTEXT_AUTHORIZING:
232 case PA_CONTEXT_SETTING_NAME:
234 case PA_CONTEXT_READY:
235 // Fetch the controls of interest
236 if (!(o = pa_context_get_source_info_list(c, get_source_list_cb, data))) {
237 qWarning() << "PaClient: get source info list: " <<
238 pa_strerror(pa_context_errno(c));
241 pa_operation_unref(o);
242 if (!(o = pa_context_get_sink_info_list(c, &get_sink_list_cb, data))) {
243 qWarning() << "PaClient: get sink info list: " <<
244 pa_strerror(pa_context_errno(c));
247 pa_operation_unref(o);
248 pa_context_set_subscribe_callback(c, subscribe_cb, data);
249 if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE), NULL, NULL))) {
250 qWarning() << "PaClient: subscribe: " <<
251 pa_strerror(pa_context_errno(c));
255 case PA_CONTEXT_TERMINATED:
259 case PA_CONTEXT_FAILED:
261 qCritical() << "PaClient: connection failed: " <<
262 pa_strerror(pa_context_errno(c));
268 void PaClient::init()
270 m_ml = pa_threaded_mainloop_new();
272 qCritical("PaClient: failed to create mainloop");
276 pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
278 m_mlapi = pa_threaded_mainloop_get_api(m_ml);
282 m_ctx = pa_context_new(m_mlapi, "Mixer");
284 qCritical("PaClient: failed to create context");
285 pa_threaded_mainloop_free(m_ml);
288 pa_context_set_state_callback(m_ctx, context_state_cb, this);
290 if (pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) {
291 qCritical("PaClient: failed to connect");
292 pa_context_unref(m_ctx);
293 pa_threaded_mainloop_free(m_ml);
297 if (pa_threaded_mainloop_start(m_ml) != 0) {
298 qCritical("PaClient: failed to start mainloop");
299 pa_context_unref(m_ctx);
300 pa_threaded_mainloop_free(m_ml);
309 void PaClient::addOneControlState(int type, int index, const pa_cvolume *cvolume)
311 pa_cvolume *cvolume_new = new pa_cvolume;
312 cvolume_new->channels = cvolume->channels;
313 for (int i = 0; i < cvolume->channels; i++)
314 cvolume_new->values[i] = cvolume->values[i];
316 m_sink_states.insert(index, cvolume_new);
317 else if (type == C_SOURCE)
318 m_source_states.insert(index, cvolume_new);
321 QHash<int, pa_cvolume *> PaClient::sink_states(void)
323 return m_sink_states;
326 QHash<int, pa_cvolume *> PaClient::source_states(void)
328 return m_source_states;