mixer: add clearer pulseaudio control naming
[apps/mixer.git] / app / paclient.cpp
1 /*
2  * Copyright (C) 2016,2017 Konsulko Group
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include "paclient.h"
18
19 #include <QtCore/QDebug>
20
21 PaClient::PaClient()
22         : m_init(false), m_ml(nullptr), m_mlapi(nullptr), m_ctx(nullptr)
23 {
24 }
25
26 PaClient::~PaClient()
27 {
28         if (m_init)
29                 close();
30 }
31
32 void PaClient::close()
33 {
34         if (!m_init) return;
35         pa_threaded_mainloop_stop(m_ml);
36         pa_threaded_mainloop_free(m_ml);
37         m_init = false;
38 }
39
40 static void set_sink_volume_cb(pa_context *c, int success, void *data)
41 {
42         Q_UNUSED(data);
43
44         if (!success)
45                 qWarning() << "PaClient: set sink volume: " <<
46                         pa_strerror(pa_context_errno(c));
47 }
48
49 static void set_source_volume_cb(pa_context *c, int success, void *data)
50 {
51         Q_UNUSED(data);
52
53         if (!success)
54                 qWarning() << "PaClient: set source volume: " <<
55                         pa_strerror(pa_context_errno(c));
56 }
57
58 void PaClient::setVolume(uint32_t type, uint32_t index, uint32_t channel, uint32_t volume)
59 {
60         pa_operation *o;
61         pa_context *c = context();
62         pa_cvolume *cvolume = NULL;
63
64         if (type == C_SINK) {
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));
71                         return;
72                 }
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));
81                         return;
82                 }
83                 pa_operation_unref(o);
84         }
85 }
86
87 void get_source_list_cb(pa_context *c,
88                 const pa_source_info *i,
89                 int eol,
90                 void *data)
91 {
92         int chan;
93
94         PaClient *self = reinterpret_cast<PaClient*>(data);
95
96         if (eol < 0) {
97                 qWarning() << "PaClient: get source list: " <<
98                         pa_strerror(pa_context_errno(c));
99
100                 self->close();
101                 return;
102         }
103
104         if (!eol) {
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"))
109                                 continue;
110
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]);
114                 }
115         }
116 }
117
118 void get_sink_list_cb(pa_context *c,
119                 const pa_sink_info *i,
120                 int eol,
121                 void *data)
122 {
123         PaClient *self = reinterpret_cast<PaClient*>(data);
124         int chan;
125
126         if(eol < 0) {
127                 qWarning() << "PaClient: get sink list: " <<
128                         pa_strerror(pa_context_errno(c));
129                 self->close();
130                 return;
131         }
132
133         if(!eol) {
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]);
139                 }
140         }
141 }
142
143 void get_sink_info_change_cb(pa_context *c,
144                              const pa_sink_info *i,
145                              int eol,
146                              void *data)
147 {
148         Q_UNUSED(c);
149         Q_ASSERT(i);
150         Q_ASSERT(data);
151
152         if (eol) return;
153
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]);
163                 }
164         }
165 }
166
167 void get_source_info_change_cb(pa_context *c,
168                                const pa_source_info *i,
169                                int eol,
170                                void *data)
171 {
172         Q_UNUSED(c);
173         Q_ASSERT(i);
174         Q_ASSERT(data);
175
176         if (eol) return;
177
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]);
187                 }
188         }
189 }
190
191
192 void subscribe_cb(pa_context *c,
193                   pa_subscription_event_type_t type,
194                   uint32_t index,
195                   void *data)
196 {
197         pa_operation *o;
198
199         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {
200                 qWarning("PaClient: unhandled subscribe event operation");
201                 return;
202         }
203
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));
209                                 return;
210                         }
211                         break;
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));
216                                 return;
217                         }
218                         break;
219                 default:
220                         qWarning("PaClient: unhandled subscribe event facility");
221         }
222 }
223
224 void context_state_cb(pa_context *c, void *data)
225 {
226         pa_operation *o;
227         PaClient *self = reinterpret_cast<PaClient*>(data);
228
229         switch (pa_context_get_state(c)) {
230                 case PA_CONTEXT_CONNECTING:
231                 case PA_CONTEXT_AUTHORIZING:
232                 case PA_CONTEXT_SETTING_NAME:
233                         break;
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));
239                                 return;
240                         }
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));
245                                 return;
246                         }
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));
252                                 return;
253                         }
254                         break;
255                 case PA_CONTEXT_TERMINATED:
256                         self->close();
257                         break;
258
259                 case PA_CONTEXT_FAILED:
260                 default:
261                         qCritical() << "PaClient: connection failed: " <<
262                                 pa_strerror(pa_context_errno(c));
263                         self->close();
264                         break;
265         }
266 }
267
268 void PaClient::init()
269 {
270         m_ml = pa_threaded_mainloop_new();
271         if (!m_ml) {
272                 qCritical("PaClient: failed to create mainloop");
273                 return;
274         }
275
276         pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
277
278         m_mlapi = pa_threaded_mainloop_get_api(m_ml);
279
280         lock();
281
282         m_ctx = pa_context_new(m_mlapi, "Mixer");
283         if (!m_ctx) {
284                 qCritical("PaClient: failed to create context");
285                 pa_threaded_mainloop_free(m_ml);
286                 return;
287         }
288         pa_context_set_state_callback(m_ctx, context_state_cb, this);
289
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);
294                 return;
295         }
296
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);
301                 return;
302         }
303
304         unlock();
305
306         m_init = true;
307 }
308
309 void PaClient::addOneControlState(int type, int index, const pa_cvolume *cvolume)
310 {
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];
315         if (type == C_SINK)
316                 m_sink_states.insert(index, cvolume_new);
317         else if (type == C_SOURCE)
318                 m_source_states.insert(index, cvolume_new);
319 }
320
321 QHash<int, pa_cvolume *> PaClient::sink_states(void)
322 {
323         return m_sink_states;
324 }
325
326 QHash<int, pa_cvolume *> PaClient::source_states(void)
327 {
328         return m_source_states;
329 }