]> code.delx.au - pulseaudio/blob - src/modules/module-volume-restore.c
fix a memory leak
[pulseaudio] / src / modules / module-volume-restore.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <unistd.h>
27 #include <assert.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34
35 #include <pulse/xmalloc.h>
36
37 #include <pulsecore/core-error.h>
38 #include <pulsecore/module.h>
39 #include <pulsecore/core-util.h>
40 #include <pulsecore/modargs.h>
41 #include <pulsecore/log.h>
42 #include <pulsecore/core-subscribe.h>
43 #include <pulsecore/sink-input.h>
44 #include <pulsecore/core-util.h>
45 #include <pulse/volume.h>
46
47 #include "module-volume-restore-symdef.h"
48
49 PA_MODULE_AUTHOR("Lennart Poettering")
50 PA_MODULE_DESCRIPTION("Automatically restore volume of playback streams")
51 PA_MODULE_USAGE("table=<filename>")
52 PA_MODULE_VERSION(PACKAGE_VERSION)
53
54 #define WHITESPACE "\n\r \t"
55
56 #define DEFAULT_VOLUME_TABLE_FILE "volume.table"
57
58 static const char* const valid_modargs[] = {
59 "table",
60 NULL,
61 };
62
63 struct rule {
64 char* name;
65 pa_cvolume volume;
66 };
67
68 struct userdata {
69 pa_hashmap *hashmap;
70 pa_subscription *subscription;
71 pa_hook_slot *hook_slot;
72 int modified;
73 char *table_file;
74 };
75
76 static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
77 char *p;
78 long k;
79 unsigned i;
80
81 assert(s);
82 assert(v);
83
84 if (!isdigit(*s))
85 return NULL;
86
87 k = strtol(s, &p, 0);
88 if (k <= 0 || k > PA_CHANNELS_MAX)
89 return NULL;
90
91 v->channels = (unsigned) k;
92
93 for (i = 0; i < v->channels; i++) {
94 p += strspn(p, WHITESPACE);
95
96 if (!isdigit(*p))
97 return NULL;
98
99 k = strtol(p, &p, 0);
100
101 if (k < PA_VOLUME_MUTED)
102 return NULL;
103
104 v->values[i] = (pa_volume_t) k;
105 }
106
107 if (*p != 0)
108 return NULL;
109
110 return v;
111 }
112
113 static int load_rules(struct userdata *u) {
114 FILE *f;
115 int n = 0;
116 int ret = -1;
117 char buf_name[256], buf_volume[256];
118 char *ln = buf_name;
119
120 f = u->table_file ?
121 fopen(u->table_file, "r") :
122 pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r");
123
124 if (!f) {
125 if (errno == ENOENT) {
126 pa_log_info("starting with empty ruleset.");
127 ret = 0;
128 } else
129 pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
130
131 goto finish;
132 }
133
134 pa_lock_fd(fileno(f), 1);
135
136 while (!feof(f)) {
137 struct rule *rule;
138 pa_cvolume v;
139
140 if (!fgets(ln, sizeof(buf_name), f))
141 break;
142
143 n++;
144
145 pa_strip_nl(ln);
146
147 if (ln[0] == '#' || !*ln )
148 continue;
149
150 if (ln == buf_name) {
151 ln = buf_volume;
152 continue;
153 }
154
155 assert(ln == buf_volume);
156
157 if (!parse_volume(buf_volume, &v)) {
158 pa_log("parse failure in %s:%u, stopping parsing", u->table_file, n);
159 goto finish;
160 }
161
162 ln = buf_name;
163
164 if (pa_hashmap_get(u->hashmap, buf_name)) {
165 pa_log("double entry in %s:%u, ignoring", u->table_file, n);
166 goto finish;
167 }
168
169 rule = pa_xnew(struct rule, 1);
170 rule->name = pa_xstrdup(buf_name);
171 rule->volume = v;
172
173 pa_hashmap_put(u->hashmap, rule->name, rule);
174 }
175
176 if (ln == buf_volume) {
177 pa_log("invalid number of lines in %s.", u->table_file);
178 goto finish;
179 }
180
181 ret = 0;
182
183 finish:
184 if (f) {
185 pa_lock_fd(fileno(f), 0);
186 fclose(f);
187 }
188
189 return ret;
190 }
191
192 static int save_rules(struct userdata *u) {
193 FILE *f;
194 int ret = -1;
195 void *state = NULL;
196 struct rule *rule;
197
198 f = u->table_file ?
199 fopen(u->table_file, "w") :
200 pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w");
201
202 if (!f) {
203 pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
204 goto finish;
205 }
206
207 pa_lock_fd(fileno(f), 1);
208
209 while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) {
210 unsigned i;
211
212 fprintf(f, "%s\n%u", rule->name, rule->volume.channels);
213
214 for (i = 0; i < rule->volume.channels; i++)
215 fprintf(f, " %u", rule->volume.values[i]);
216
217 fprintf(f, "\n");
218 }
219
220 ret = 0;
221
222 finish:
223 if (f) {
224 pa_lock_fd(fileno(f), 0);
225 fclose(f);
226 }
227
228 return ret;
229 }
230
231 static char* client_name(pa_client *c) {
232 char *t, *e;
233
234 if (!c->name || !c->driver)
235 return NULL;
236
237 t = pa_sprintf_malloc("%s$%s", c->driver, c->name);
238 t[strcspn(t, "\n\r#")] = 0;
239
240 if (!*t) {
241 pa_xfree(t);
242 return NULL;
243 }
244
245 if ((e = strrchr(t, '('))) {
246 char *k = e + 1 + strspn(e + 1, "0123456789-");
247
248 /* Dirty trick: truncate all trailing parens with numbers in
249 * between, since they are usually used to identify multiple
250 * sessions of the same application, which is something we
251 * explicitly don't want. Besides other stuff this makes xmms
252 * with esound work properly for us. */
253
254 if (*k == ')' && *(k+1) == 0)
255 *e = 0;
256 }
257
258 return t;
259 }
260
261 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
262 struct userdata *u = userdata;
263 pa_sink_input *si;
264 struct rule *r;
265 char *name;
266
267 assert(c);
268 assert(u);
269
270 if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
271 t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
272 return;
273
274 if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
275 return;
276
277 if (!si->client || !(name = client_name(si->client)))
278 return;
279
280 if ((r = pa_hashmap_get(u->hashmap, name))) {
281 pa_xfree(name);
282
283 if (!pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
284 pa_log_info("Saving volume for <%s>", r->name);
285 r->volume = *pa_sink_input_get_volume(si);
286 u->modified = 1;
287 }
288 } else {
289 pa_log_info("Creating new entry for <%s>", name);
290
291 r = pa_xnew(struct rule, 1);
292 r->name = name;
293 r->volume = *pa_sink_input_get_volume(si);
294 pa_hashmap_put(u->hashmap, r->name, r);
295
296 u->modified = 1;
297 }
298 }
299
300 static pa_hook_result_t hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
301 struct rule *r;
302 char *name;
303
304 assert(data);
305
306 if (!data->client || !(name = client_name(data->client)))
307 return PA_HOOK_OK;
308
309 if ((r = pa_hashmap_get(u->hashmap, name))) {
310
311 if (data->sample_spec_is_set && data->sample_spec.channels == r->volume.channels) {
312 pa_log_info("Restoring volume for <%s>", r->name);
313 pa_sink_input_new_data_set_volume(data, &r->volume);
314 }
315 }
316
317 return PA_HOOK_OK;
318 }
319
320 int pa__init(pa_core *c, pa_module*m) {
321 pa_modargs *ma = NULL;
322 struct userdata *u;
323
324 assert(c);
325 assert(m);
326
327 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
328 pa_log("Failed to parse module arguments");
329 goto fail;
330 }
331
332 u = pa_xnew(struct userdata, 1);
333 u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
334 u->subscription = NULL;
335 u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));
336 u->modified = 0;
337
338 m->userdata = u;
339
340 if (load_rules(u) < 0)
341 goto fail;
342
343 u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscribe_callback, u);
344 u->hook_slot = pa_hook_connect(&c->hook_sink_input_new, (pa_hook_cb_t) hook_callback, u);
345
346 pa_modargs_free(ma);
347 return 0;
348
349 fail:
350 pa__done(c, m);
351
352 if (ma)
353 pa_modargs_free(ma);
354
355 return -1;
356 }
357
358 static void free_func(void *p, void *userdata) {
359 struct rule *r = p;
360 assert(r);
361
362 pa_xfree(r->name);
363 pa_xfree(r);
364 }
365
366 void pa__done(pa_core *c, pa_module*m) {
367 struct userdata* u;
368
369 assert(c);
370 assert(m);
371
372 if (!(u = m->userdata))
373 return;
374
375 if (u->subscription)
376 pa_subscription_free(u->subscription);
377
378 if (u->hook_slot)
379 pa_hook_slot_free(u->hook_slot);
380
381 if (u->hashmap) {
382
383 if (u->modified)
384 save_rules(u);
385
386 pa_hashmap_free(u->hashmap, free_func, NULL);
387 }
388
389 pa_xfree(u->table_file);
390 pa_xfree(u);
391 }
392
393