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