AGL-style PulseAudio mixer app
[apps/mixer.git] / app / pac.c
1 /*
2  * Copyright (C) 2016 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 <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <signal.h>
21 #include <errno.h>
22 #include <unistd.h>
23 #include <limits.h>
24 #include <assert.h>
25
26 #include <pulse/pulseaudio.h>
27 #include <sys/queue.h>
28
29 #include "pacontrolmodel.h"
30 #include "pac.h"
31
32 /* FIXME: move these into a pac context */
33 static pa_threaded_mainloop* m;
34 TAILQ_HEAD(pac_cstateq, pac_cstate);
35 static struct pac_cstateq cstateq;
36
37 static void add_one_cstate(int type, int index, const pa_cvolume *cvolume)
38 {
39         struct pac_cstate *cstate;
40         int i;
41
42         cstate = pa_xnew(struct pac_cstate, 1);
43         cstate->type = type;
44         cstate->index = index;
45         cstate->cvolume.channels = cvolume->channels;
46         for (i = 0; i < cvolume->channels; i++)
47                 cstate->cvolume.values[i] = cvolume->values[i];
48
49         TAILQ_INSERT_TAIL(&cstateq, cstate, tailq);
50 }
51
52 static void get_source_list_cb(pa_context *c,
53                 const pa_source_info *i,
54                 int eol,
55                 void *data)
56 {
57         int chan;
58
59         if (eol < 0) {
60                 fprintf(stderr, "get source list: %s\n",
61                                 pa_strerror(pa_context_errno(c)));
62
63                 pa_threaded_mainloop_stop(m);
64                 return;
65         }
66
67         if (!eol) {
68                 assert(i);
69                 add_one_cstate(C_SOURCE, i->index, &i->volume);
70                 for (chan = 0; chan < i->channel_map.channels; chan++) {
71                         add_one_control(data, i->index, i->description,
72                                         C_SOURCE, chan,
73                                         channel_position_string[i->channel_map.map[chan]],
74                                         i->volume.values[chan]);
75                 }
76         }
77 }
78
79 static void get_sink_list_cb(pa_context *c,
80                 const pa_sink_info *i,
81                 int eol,
82                 void *data)
83 {
84         int chan;
85
86         if(eol < 0) {
87                 fprintf(stderr, "get sink list: %s\n",
88                                 pa_strerror(pa_context_errno(c)));
89
90                 pa_threaded_mainloop_stop(m);
91                 return;
92         }
93
94         if(!eol) {
95                 assert(i);
96                 add_one_cstate(C_SINK, i->index, &i->volume);
97                 for (chan = 0; chan < i->channel_map.channels; chan++) {
98                         add_one_control(data, i->index, i->description,
99                                         C_SINK, chan,
100                                         channel_position_string[i->channel_map.map[chan]],
101                                         i->volume.values[chan]);
102                 }
103         }
104 }
105
106 static void context_state_cb(pa_context *c, void *data) {
107         pa_operation *o;
108
109         assert(c);
110         switch (pa_context_get_state(c)) {
111                 case PA_CONTEXT_CONNECTING:
112                 case PA_CONTEXT_AUTHORIZING:
113                 case PA_CONTEXT_SETTING_NAME:
114                         break;
115                 case PA_CONTEXT_READY:
116                         /* Fetch the controls of interest */
117                         if (!(o = pa_context_get_source_info_list(c, get_source_list_cb, data))) {
118                                 fprintf(stderr, "get source info list: %s\n",
119                                         pa_strerror(pa_context_errno(c)));
120                                 return;
121                         }
122                         pa_operation_unref(o);
123                         if (!(o = pa_context_get_sink_info_list(c, get_sink_list_cb, data))) {
124                                 fprintf(stderr, "get sink info list: %s\n",
125                                         pa_strerror(pa_context_errno(c)));
126                                 return;
127                         }
128                         break;
129                 case PA_CONTEXT_TERMINATED:
130                         pa_threaded_mainloop_stop(m);
131                         break;
132                 case PA_CONTEXT_FAILED:
133                 default:
134                         fprintf(stderr, "PA connection failed: %s\n",
135                                         pa_strerror(pa_context_errno(c)));
136                         pa_threaded_mainloop_stop(m);
137         }
138 }
139
140 static void pac_set_source_volume_cb(pa_context *c, int success, void *userdata __attribute__((unused))) {
141         assert(c);
142         if (!success)
143                 fprintf(stderr, "Set source volume: %s\n",
144                                 pa_strerror(pa_context_errno(c)));
145 }
146
147 static void pac_set_sink_volume_cb(pa_context *c, int success, void *userdata __attribute__((unused))) {
148         assert(c);
149         if (!success)
150                 fprintf(stderr, "Set source volume: %s\n",
151                                 pa_strerror(pa_context_errno(c)));
152 }
153
154 void pac_set_volume(pa_context *c, uint32_t type, uint32_t idx, uint32_t channel, uint32_t volume)
155 {
156         pa_operation *o;
157         struct pac_cstate *cstate;
158
159         TAILQ_FOREACH(cstate, &cstateq, tailq)
160                 if (cstate->index == idx)
161                         break;
162         cstate->cvolume.values[channel] = volume;
163
164         if (type == C_SOURCE) {
165                 if (!(o = pa_context_set_source_volume_by_index(c, idx, &cstate->cvolume, pac_set_source_volume_cb, NULL))) {
166                         fprintf(stderr, "set source #%d channel #%d volume: %s\n",
167                                 idx, channel, pa_strerror(pa_context_errno(c)));
168                         return;
169                 }
170                 pa_operation_unref(o);
171         } else if (type == C_SINK) {
172                 if (!(o = pa_context_set_sink_volume_by_index(c, idx, &cstate->cvolume, pac_set_sink_volume_cb, NULL))) {
173                         fprintf(stderr, "set sink #%d channel #%d volume: %s\n",
174                                 idx, channel, pa_strerror(pa_context_errno(c)));
175                         return;
176                 }
177                 pa_operation_unref(o);
178         }
179 }
180
181 pa_context *pac_init(void *this, const char *name) {
182         pa_context *c;
183         pa_mainloop_api *mapi;
184         char *server = NULL;
185         char *client = pa_xstrdup(name);
186
187         TAILQ_INIT(&cstateq);
188
189         if (!(m = pa_threaded_mainloop_new())) {
190                 fprintf(stderr, "pa_mainloop_new() failed.\n");
191                 return NULL;
192         }
193
194         pa_threaded_mainloop_set_name(m, "pa_mainloop");
195         mapi = pa_threaded_mainloop_get_api(m);
196
197         if (!(c = pa_context_new(mapi, client))) {
198                 fprintf(stderr, "pa_context_new() failed.\n");
199                 goto exit;
200         }
201
202         pa_context_set_state_callback(c, context_state_cb, this);
203         if (pa_context_connect(c, server, 0, NULL) < 0) {
204                 fprintf(stderr, "pa_context_connect(): %s", pa_strerror(pa_context_errno(c)));
205                 goto exit;
206         }
207
208         if (pa_threaded_mainloop_start(m) < 0) {
209                 fprintf(stderr, "pa_mainloop_run() failed.\n");
210                 goto exit;
211         }
212
213         return c;
214
215 exit:
216         if (c)
217                 pa_context_unref(c);
218
219         if (m)
220                 pa_threaded_mainloop_free(m);
221
222         pa_xfree(client);
223
224         return NULL;
225 }