]> code.delx.au - pulseaudio/blob - src/modules/module-volume-restore.c
Wrap strerror() in a function that makes it thread safe and converts the
[pulseaudio] / src / modules / module-volume-restore.c
1 /* $Id$ */
2
3 /***
4 This file is part of polypaudio.
5
6 polypaudio 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 polypaudio 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 polypaudio; 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 <polyp/error.h>
36 #include <polyp/xmalloc.h>
37
38 #include <polypcore/module.h>
39 #include <polypcore/core-util.h>
40 #include <polypcore/modargs.h>
41 #include <polypcore/log.h>
42 #include <polypcore/core-subscribe.h>
43 #include <polypcore/sink-input.h>
44 #include <polypcore/core-util.h>
45 #include <polyp/volume.h>
46
47 #include "module-volume-restore-symdef.h"
48
49 PA_MODULE_AUTHOR("Lennart Poettering")
50 PA_MODULE_DESCRIPTION("Playback stream automatic volume restore module")
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 ".polypaudio/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 int modified;
72 char *table_file;
73 };
74
75 static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
76 char *p;
77 long k;
78 unsigned i;
79
80 assert(s);
81 assert(v);
82
83 if (!isdigit(*s))
84 return NULL;
85
86 k = strtol(s, &p, 0);
87 if (k <= 0 || k > PA_CHANNELS_MAX)
88 return NULL;
89
90 v->channels = (unsigned) k;
91
92 for (i = 0; i < v->channels; i++) {
93 p += strspn(p, WHITESPACE);
94
95 if (!isdigit(*p))
96 return NULL;
97
98 k = strtol(p, &p, 0);
99
100 if (k < PA_VOLUME_MUTED)
101 return NULL;
102
103 v->values[i] = (pa_volume_t) k;
104 }
105
106 if (*p != 0)
107 return NULL;
108
109 return v;
110 }
111
112 static int load_rules(struct userdata *u) {
113 FILE *f;
114 int n = 0;
115 int ret = -1;
116 char buf_name[256], buf_volume[256];
117 char *ln = buf_name;
118
119 f = u->table_file ?
120 fopen(u->table_file, "r") :
121 pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r");
122
123 if (!f) {
124 if (errno == ENOENT) {
125 pa_log_info(__FILE__": starting with empty ruleset.");
126 ret = 0;
127 } else
128 pa_log(__FILE__": failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
129
130 goto finish;
131 }
132
133 pa_lock_fd(fileno(f), 1);
134
135 while (!feof(f)) {
136 struct rule *rule;
137 pa_cvolume v;
138
139 if (!fgets(ln, sizeof(buf_name), f))
140 break;
141
142 n++;
143
144 pa_strip_nl(ln);
145
146 if (ln[0] == '#' || !*ln )
147 continue;
148
149 if (ln == buf_name) {
150 ln = buf_volume;
151 continue;
152 }
153
154 assert(ln == buf_volume);
155
156 if (!parse_volume(buf_volume, &v)) {
157 pa_log(__FILE__": parse failure in %s:%u, stopping parsing", u->table_file, n);
158 goto finish;
159 }
160
161 ln = buf_name;
162
163 if (pa_hashmap_get(u->hashmap, buf_name)) {
164 pa_log(__FILE__": double entry in %s:%u, ignoring", u->table_file, n);
165 goto finish;
166 }
167
168 rule = pa_xnew(struct rule, 1);
169 rule->name = pa_xstrdup(buf_name);
170 rule->volume = v;
171
172 pa_hashmap_put(u->hashmap, rule->name, rule);
173 }
174
175 if (ln == buf_volume) {
176 pa_log(__FILE__": invalid number of lines in %s.", u->table_file);
177 goto finish;
178 }
179
180 ret = 0;
181
182 finish:
183 if (f) {
184 pa_lock_fd(fileno(f), 0);
185 fclose(f);
186 }
187
188 return ret;
189 }
190
191 static int save_rules(struct userdata *u) {
192 FILE *f;
193 int ret = -1;
194 void *state = NULL;
195 struct rule *rule;
196
197 f = u->table_file ?
198 fopen(u->table_file, "w") :
199 pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w");
200
201 if (!f) {
202 pa_log(__FILE__": failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
203 goto finish;
204 }
205
206 pa_lock_fd(fileno(f), 1);
207
208 while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) {
209 unsigned i;
210
211 fprintf(f, "%s\n%u", rule->name, rule->volume.channels);
212
213 for (i = 0; i < rule->volume.channels; i++)
214 fprintf(f, " %u", rule->volume.values[i]);
215
216 fprintf(f, "\n");
217 }
218
219 ret = 0;
220
221 finish:
222 if (f) {
223 pa_lock_fd(fileno(f), 0);
224 fclose(f);
225 }
226
227 return ret;
228 }
229
230 static char* client_name(pa_client *c) {
231 char *t, *e;
232
233 if (!c->name || !c->driver)
234 return NULL;
235
236 t = pa_sprintf_malloc("%s$%s", c->driver, c->name);
237 t[strcspn(t, "\n\r#")] = 0;
238
239 if (!*t)
240 return NULL;
241
242 if ((e = strrchr(t, '('))) {
243 char *k = e + 1 + strspn(e + 1, "0123456789-");
244
245 /* Dirty trick: truncate all trailing parens with numbers in
246 * between, since they are usually used to identify multiple
247 * sessions of the same application, which is something we
248 * explicitly don't want. Besides other stuff this makes xmms
249 * with esound work properly for us. */
250
251 if (*k == ')' && *(k+1) == 0)
252 *e = 0;
253 }
254
255 return t;
256 }
257
258 static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
259 struct userdata *u = userdata;
260 pa_sink_input *si;
261 struct rule *r;
262 char *name;
263
264 assert(c);
265 assert(u);
266
267 if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
268 t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
269 return;
270
271 if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
272 return;
273
274 if (!si->client || !(name = client_name(si->client)))
275 return;
276
277 if ((r = pa_hashmap_get(u->hashmap, name))) {
278 pa_xfree(name);
279
280 if (((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) && si->sample_spec.channels == r->volume.channels) {
281 pa_log_info(__FILE__": Restoring volume for <%s>", r->name);
282 pa_sink_input_set_volume(si, &r->volume);
283 } else if (!pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
284 pa_log_info(__FILE__": Saving volume for <%s>", r->name);
285 r->volume = *pa_sink_input_get_volume(si);
286 u->modified = 1;
287 }
288
289 } else {
290 pa_log_info(__FILE__": Creating new entry for <%s>", name);
291
292 r = pa_xnew(struct rule, 1);
293 r->name = name;
294 r->volume = *pa_sink_input_get_volume(si);
295 pa_hashmap_put(u->hashmap, r->name, r);
296
297 u->modified = 1;
298 }
299 }
300
301 int pa__init(pa_core *c, pa_module*m) {
302 pa_modargs *ma = NULL;
303 struct userdata *u;
304
305 assert(c);
306 assert(m);
307
308 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
309 pa_log(__FILE__": Failed to parse module arguments");
310 goto fail;
311 }
312
313 u = pa_xnew(struct userdata, 1);
314 u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
315 u->subscription = NULL;
316 u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));
317 u->modified = 0;
318
319 m->userdata = u;
320
321 if (load_rules(u) < 0)
322 goto fail;
323
324 u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
325
326 pa_modargs_free(ma);
327 return 0;
328
329 fail:
330 pa__done(c, m);
331
332 if (ma)
333 pa_modargs_free(ma);
334
335 return -1;
336 }
337
338 static void free_func(void *p, void *userdata) {
339 struct rule *r = p;
340 assert(r);
341
342 pa_xfree(r->name);
343 pa_xfree(r);
344 }
345
346 void pa__done(pa_core *c, pa_module*m) {
347 struct userdata* u;
348
349 assert(c);
350 assert(m);
351
352 if (!(u = m->userdata))
353 return;
354
355 if (u->subscription)
356 pa_subscription_free(u->subscription);
357
358 if (u->hashmap) {
359
360 if (u->modified)
361 save_rules(u);
362
363 pa_hashmap_free(u->hashmap, free_func, NULL);
364 }
365
366 pa_xfree(u->table_file);
367 pa_xfree(u);
368 }
369
370