Fix source volume setting in PaClient::setVolume
[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 __attribute__((unused)))
41 {
42         if (!success)
43                 qWarning() << "PaClient: set sink volume: " <<
44                         pa_strerror(pa_context_errno(c));
45 }
46
47 static void set_source_volume_cb(pa_context *c, int success, void *data __attribute__((unused)))
48 {
49         if (!success)
50                 qWarning() << "PaClient: set source volume: " <<
51                         pa_strerror(pa_context_errno(c));
52 }
53
54 void PaClient::setVolume(uint32_t type, uint32_t index, uint32_t channel, uint32_t volume)
55 {
56         pa_operation *o;
57         pa_context *c = context();
58         pa_cvolume *cvolume = NULL;
59
60         if (type == C_SINK) {
61                 cvolume = m_sink_states.value(index);
62                 cvolume->values[channel] = volume;
63                 if (!(o = pa_context_set_sink_volume_by_index(c, index, cvolume, set_sink_volume_cb, NULL))) {
64                         qWarning() << "PaClient: set sink #" << index <<
65                                 " channel #" << channel <<
66                                 " volume: " << pa_strerror(pa_context_errno(c));
67                         return;
68                 }
69                 pa_operation_unref(o);
70         } else if (type == C_SOURCE) {
71                 cvolume = m_source_states.value(index);
72                 cvolume->values[channel] = volume;
73                 if (!(o = pa_context_set_source_volume_by_index(c, index, cvolume, set_source_volume_cb, NULL))) {
74                         qWarning() << "PaClient: set source #" << index <<
75                                 " channel #" << channel <<
76                                 " volume: " << pa_strerror(pa_context_errno(c));
77                         return;
78                 }
79                 pa_operation_unref(o);
80         }
81 }
82
83 void get_source_list_cb(pa_context *c,
84                 const pa_source_info *i,
85                 int eol,
86                 void *data)
87 {
88         int chan;
89
90         PaClient *self = reinterpret_cast<PaClient*>(data);
91
92         if (eol < 0) {
93                 qWarning() << "PaClient: get source list: " <<
94                         pa_strerror(pa_context_errno(c));
95
96                 self->close();
97                 return;
98         }
99
100         if (!eol) {
101                 self->addOneControlState(C_SOURCE, i->index, &i->volume);
102                 for (chan = 0; chan < i->channel_map.channels; chan++) {
103                         emit self->controlAdded(i->index, QString(i->description), C_SOURCE, chan,
104                                                 channel_position_string[i->channel_map.map[chan]],
105                                                 i->volume.values[chan]);
106                 }
107         }
108 }
109
110 void get_sink_list_cb(pa_context *c,
111                 const pa_sink_info *i,
112                 int eol,
113                 void *data)
114 {
115         int chan;
116         PaClient *self = reinterpret_cast<PaClient*>(data);
117
118         if(eol < 0) {
119                 qWarning() << "PaClient: get sink list: " <<
120                         pa_strerror(pa_context_errno(c));
121                 self->close();
122                 return;
123         }
124
125         if(!eol) {
126                 self->addOneControlState(C_SINK, i->index, &i->volume);
127                 for (chan = 0; chan < i->channel_map.channels; chan++) {
128                         emit self->controlAdded(i->index, QString(i->description), C_SINK, chan,
129                                                  channel_position_string[i->channel_map.map[chan]],
130                                                  i->volume.values[chan]);
131                 }
132         }
133 }
134
135 void context_state_cb(pa_context *c, void *data)
136 {
137         pa_operation *o;
138         PaClient *self = reinterpret_cast<PaClient*>(data);
139
140         switch (pa_context_get_state(c)) {
141                 case PA_CONTEXT_CONNECTING:
142                 case PA_CONTEXT_AUTHORIZING:
143                 case PA_CONTEXT_SETTING_NAME:
144                         break;
145                 case PA_CONTEXT_READY:
146                         // Fetch the controls of interest
147                         if (!(o = pa_context_get_source_info_list(c, get_source_list_cb, data))) {
148                                 qWarning() << "PaClient: get source info list: " <<
149                                         pa_strerror(pa_context_errno(c));
150                                 return;
151                         }
152                         pa_operation_unref(o);
153                         if (!(o = pa_context_get_sink_info_list(c, &get_sink_list_cb, data))) {
154                                 qWarning() << "PaClient: get sink info list: " <<
155                                         pa_strerror(pa_context_errno(c));
156                                 return;
157                         }
158                         break;
159                 case PA_CONTEXT_TERMINATED:
160                         self->close();
161                         break;
162
163                 case PA_CONTEXT_FAILED:
164                 default:
165                         qCritical() << "PaClient: connection failed: " <<
166                                 pa_strerror(pa_context_errno(c));
167                         self->close();
168                         break;
169         }
170 }
171
172 void PaClient::init()
173 {
174         m_ml = pa_threaded_mainloop_new();
175         if (!m_ml) {
176                 qCritical("PaClient: failed to create mainloop");
177                 return;
178         }
179
180         pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
181
182         m_mlapi = pa_threaded_mainloop_get_api(m_ml);
183
184         lock();
185
186         m_ctx = pa_context_new(m_mlapi, "Mixer");
187         if (!m_ctx) {
188                 qCritical("PaClient: failed to create context");
189                 pa_threaded_mainloop_free(m_ml);
190                 return;
191         }
192         pa_context_set_state_callback(m_ctx, context_state_cb, this);
193
194         if (pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) {
195                 qCritical("PaClient: failed to connect");
196                 pa_context_unref(m_ctx);
197                 pa_threaded_mainloop_free(m_ml);
198                 return;
199         }
200
201         if (pa_threaded_mainloop_start(m_ml) != 0) {
202                 qCritical("PaClient: failed to start mainloop");
203                 pa_context_unref(m_ctx);
204                 pa_threaded_mainloop_free(m_ml);
205                 return;
206         }
207
208         unlock();
209
210         m_init = true;
211 }
212
213 void PaClient::addOneControlState(int type, int index, const pa_cvolume *cvolume)
214 {
215         int i;
216         pa_cvolume *cvolume_new;
217
218         cvolume_new = new pa_cvolume;
219         cvolume_new->channels = cvolume->channels;
220         for (i = 0; i < cvolume->channels; i++)
221                 cvolume_new->values[i] = cvolume->values[i];
222         if (type == C_SINK)
223                 m_sink_states.insert(index, cvolume_new);
224         else if (type == C_SOURCE)
225                 m_source_states.insert(index, cvolume_new);
226 }