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