]> code.delx.au - pulseaudio/blob - src/modules/module-volume-restore.c
modify module-volume-restore to change the initial volume of a sink input from a...
[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("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 "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(__FILE__": starting with empty ruleset.");
127 ret = 0;
128 } else
129 pa_log(__FILE__": 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(__FILE__": 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(__FILE__": 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(__FILE__": 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(__FILE__": 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 return NULL;
242
243 if ((e = strrchr(t, '('))) {
244 char *k = e + 1 + strspn(e + 1, "0123456789-");
245
246 /* Dirty trick: truncate all trailing parens with numbers in
247 * between, since they are usually used to identify multiple
248 * sessions of the same application, which is something we
249 * explicitly don't want. Besides other stuff this makes xmms
250 * with esound work properly for us. */
251
252 if (*k == ')' && *(k+1) == 0)
253 *e = 0;
254 }
255
256 return t;
257 }
258
259 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
260 struct userdata *u = userdata;
261 pa_sink_input *si;
262 struct rule *r;
263 char *name;
264
265 assert(c);
266 assert(u);
267
268 if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
269 t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
270 return;
271
272 if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
273 return;
274
275 if (!si->client || !(name = client_name(si->client)))
276 return;
277
278 if ((r = pa_hashmap_get(u->hashmap, name))) {
279 pa_xfree(name);
280
281 if (!pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
282 pa_log_info(__FILE__": Saving volume for <%s>", r->name);
283 r->volume = *pa_sink_input_get_volume(si);
284 u->modified = 1;
285 }
286 } else {
287 pa_log_info(__FILE__": Creating new entry for <%s>", name);
288
289 r = pa_xnew(struct rule, 1);
290 r->name = name;
291 r->volume = *pa_sink_input_get_volume(si);
292 pa_hashmap_put(u->hashmap, r->name, r);
293
294 u->modified = 1;
295 }
296 }
297
298 static pa_hook_result_t hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
299 struct rule *r;
300 char *name;
301
302 assert(data);
303
304 if (!data->client || !(name = client_name(data->client)))
305 return PA_HOOK_OK;
306
307 if ((r = pa_hashmap_get(u->hashmap, name))) {
308
309 if (data->sample_spec_is_set && data->sample_spec.channels == r->volume.channels) {
310 pa_log_info(__FILE__": Restoring volume for <%s>", r->name);
311 pa_sink_input_new_data_set_volume(data, &r->volume);
312 }
313 }
314
315 return PA_HOOK_OK;
316 }
317
318 int pa__init(pa_core *c, pa_module*m) {
319 pa_modargs *ma = NULL;
320 struct userdata *u;
321
322 assert(c);
323 assert(m);
324
325 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
326 pa_log(__FILE__": Failed to parse module arguments");
327 goto fail;
328 }
329
330 u = pa_xnew(struct userdata, 1);
331 u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
332 u->subscription = NULL;
333 u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));
334 u->modified = 0;
335
336 m->userdata = u;
337
338 if (load_rules(u) < 0)
339 goto fail;
340
341 u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscribe_callback, u);
342 u->hook_slot = pa_hook_connect(&c->hook_sink_input_new, (pa_hook_cb_t) hook_callback, u);
343
344 pa_modargs_free(ma);
345 return 0;
346
347 fail:
348 pa__done(c, m);
349
350 if (ma)
351 pa_modargs_free(ma);
352
353 return -1;
354 }
355
356 static void free_func(void *p, void *userdata) {
357 struct rule *r = p;
358 assert(r);
359
360 pa_xfree(r->name);
361 pa_xfree(r);
362 }
363
364 void pa__done(pa_core *c, pa_module*m) {
365 struct userdata* u;
366
367 assert(c);
368 assert(m);
369
370 if (!(u = m->userdata))
371 return;
372
373 if (u->subscription)
374 pa_subscription_free(u->subscription);
375
376 if (u->hook_slot)
377 pa_hook_slot_free(u->hook_slot);
378
379 if (u->hashmap) {
380
381 if (u->modified)
382 save_rules(u);
383
384 pa_hashmap_free(u->hashmap, free_func, NULL);
385 }
386
387 pa_xfree(u->table_file);
388 pa_xfree(u);
389 }
390
391