]> code.delx.au - pulseaudio/blob - src/modules/module-volume-restore.c
bdfd3816c3f6d53d8ecc3a0b16cc3fcbd4900ff8
[pulseaudio] / src / modules / module-volume-restore.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006 Lennart Poettering
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 <string.h>
28 #include <errno.h>
29 #include <sys/types.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <ctype.h>
33
34 #include <pulse/xmalloc.h>
35 #include <pulse/volume.h>
36 #include <pulse/timeval.h>
37
38 #include <pulsecore/core-error.h>
39 #include <pulsecore/module.h>
40 #include <pulsecore/core-util.h>
41 #include <pulsecore/modargs.h>
42 #include <pulsecore/log.h>
43 #include <pulsecore/core-subscribe.h>
44 #include <pulsecore/sink-input.h>
45 #include <pulsecore/source-output.h>
46 #include <pulsecore/namereg.h>
47
48 #include "module-volume-restore-symdef.h"
49
50 PA_MODULE_AUTHOR("Lennart Poettering");
51 PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(TRUE);
54 PA_MODULE_USAGE(
55 "table=<filename> "
56 "restore_device=<Restore the device for each stream?> "
57 "restore_volume=<Restore the volume for each stream?>"
58 );
59
60 #define WHITESPACE "\n\r \t"
61 #define DEFAULT_VOLUME_TABLE_FILE "volume-restore.table"
62 #define SAVE_INTERVAL 10
63
64 static const char* const valid_modargs[] = {
65 "table",
66 "restore_device",
67 "restore_volume",
68 NULL,
69 };
70
71 struct rule {
72 char* name;
73 pa_bool_t volume_is_set;
74 pa_cvolume volume;
75 char *sink, *source;
76 };
77
78 struct userdata {
79 pa_core *core;
80 pa_hashmap *hashmap;
81 pa_subscription *subscription;
82 pa_hook_slot
83 *sink_input_new_hook_slot,
84 *sink_input_fixate_hook_slot,
85 *source_output_new_hook_slot;
86 pa_bool_t modified;
87 char *table_file;
88 pa_time_event *save_time_event;
89 };
90
91 static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
92 char *p;
93 long k;
94 unsigned i;
95
96 pa_assert(s);
97 pa_assert(v);
98
99 if (!isdigit(*s))
100 return NULL;
101
102 k = strtol(s, &p, 0);
103 if (k <= 0 || k > (long) PA_CHANNELS_MAX)
104 return NULL;
105
106 v->channels = (uint8_t) k;
107
108 for (i = 0; i < v->channels; i++) {
109 p += strspn(p, WHITESPACE);
110
111 if (!isdigit(*p))
112 return NULL;
113
114 k = strtol(p, &p, 0);
115
116 if (k < (long) PA_VOLUME_MUTED)
117 return NULL;
118
119 v->values[i] = (pa_volume_t) k;
120 }
121
122 if (*p != 0)
123 return NULL;
124
125 return v;
126 }
127
128 static int load_rules(struct userdata *u) {
129 FILE *f;
130 int n = 0;
131 int ret = -1;
132 char buf_name[256], buf_volume[256], buf_sink[256], buf_source[256];
133 char *ln = buf_name;
134
135 if (!(f = fopen(u->table_file, "r"))) {
136 if (errno == ENOENT) {
137 pa_log_info("Starting with empty ruleset.");
138 ret = 0;
139 } else
140 pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
141
142 goto finish;
143 }
144
145 pa_lock_fd(fileno(f), 1);
146
147 while (!feof(f)) {
148 struct rule *rule;
149 pa_cvolume v;
150 pa_bool_t v_is_set;
151
152 if (!fgets(ln, sizeof(buf_name), f))
153 break;
154
155 n++;
156
157 pa_strip_nl(ln);
158
159 if (ln[0] == '#')
160 continue;
161
162 if (ln == buf_name) {
163 ln = buf_volume;
164 continue;
165 }
166
167 if (ln == buf_volume) {
168 ln = buf_sink;
169 continue;
170 }
171
172 if (ln == buf_sink) {
173 ln = buf_source;
174 continue;
175 }
176
177 pa_assert(ln == buf_source);
178
179 if (buf_volume[0]) {
180 if (!parse_volume(buf_volume, &v)) {
181 pa_log("parse failure in %s:%u, stopping parsing", u->table_file, n);
182 goto finish;
183 }
184
185 v_is_set = TRUE;
186 } else
187 v_is_set = FALSE;
188
189 ln = buf_name;
190
191 if (pa_hashmap_get(u->hashmap, buf_name)) {
192 pa_log("double entry in %s:%u, ignoring", u->table_file, n);
193 continue;
194 }
195
196 rule = pa_xnew(struct rule, 1);
197 rule->name = pa_xstrdup(buf_name);
198 if ((rule->volume_is_set = v_is_set))
199 rule->volume = v;
200 rule->sink = buf_sink[0] ? pa_xstrdup(buf_sink) : NULL;
201 rule->source = buf_source[0] ? pa_xstrdup(buf_source) : NULL;
202
203 pa_hashmap_put(u->hashmap, rule->name, rule);
204 }
205
206 if (ln != buf_name) {
207 pa_log("invalid number of lines in %s.", u->table_file);
208 goto finish;
209 }
210
211 ret = 0;
212
213 finish:
214 if (f) {
215 pa_lock_fd(fileno(f), 0);
216 fclose(f);
217 }
218
219 return ret;
220 }
221
222 static int save_rules(struct userdata *u) {
223 FILE *f;
224 int ret = -1;
225 void *state = NULL;
226 struct rule *rule;
227
228 if (!u->modified)
229 return 0;
230
231 pa_log_info("Saving rules...");
232
233 if (!(f = fopen(u->table_file, "w"))) {
234 pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
235 goto finish;
236 }
237
238 pa_lock_fd(fileno(f), 1);
239
240 while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) {
241 unsigned i;
242
243 fprintf(f, "%s\n", rule->name);
244
245 if (rule->volume_is_set) {
246 fprintf(f, "%u", rule->volume.channels);
247
248 for (i = 0; i < rule->volume.channels; i++)
249 fprintf(f, " %u", rule->volume.values[i]);
250 }
251
252 fprintf(f, "\n%s\n%s\n",
253 rule->sink ? rule->sink : "",
254 rule->source ? rule->source : "");
255 }
256
257 ret = 0;
258 u->modified = FALSE;
259 pa_log_debug("Successfully saved rules...");
260
261 finish:
262 if (f) {
263 pa_lock_fd(fileno(f), 0);
264 fclose(f);
265 }
266
267 return ret;
268 }
269
270 static char* client_name(pa_client *c) {
271 char *t, *e;
272
273 if (!pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME) || !c->driver)
274 return NULL;
275
276 t = pa_sprintf_malloc("%s$%s", c->driver, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME));
277 t[strcspn(t, "\n\r#")] = 0;
278
279 if (!*t) {
280 pa_xfree(t);
281 return NULL;
282 }
283
284 if ((e = strrchr(t, '('))) {
285 char *k = e + 1 + strspn(e + 1, "0123456789-");
286
287 /* Dirty trick: truncate all trailing parens with numbers in
288 * between, since they are usually used to identify multiple
289 * sessions of the same application, which is something we
290 * explicitly don't want. Besides other stuff this makes xmms
291 * with esound work properly for us. */
292
293 if (*k == ')' && *(k+1) == 0)
294 *e = 0;
295 }
296
297 return t;
298 }
299
300 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
301 struct userdata *u = userdata;
302
303 pa_assert(a);
304 pa_assert(e);
305 pa_assert(tv);
306 pa_assert(u);
307
308 pa_assert(e == u->save_time_event);
309 u->core->mainloop->time_free(u->save_time_event);
310 u->save_time_event = NULL;
311
312 save_rules(u);
313 }
314
315 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
316 struct userdata *u = userdata;
317 pa_sink_input *si = NULL;
318 pa_source_output *so = NULL;
319 struct rule *r;
320 char *name;
321
322 pa_assert(c);
323 pa_assert(u);
324
325 if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
326 t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
327 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
328 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
329 return;
330
331 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
332 if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
333 return;
334
335 if (!si->client || !(name = client_name(si->client)))
336 return;
337 } else {
338 pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
339
340 if (!(so = pa_idxset_get_by_index(c->source_outputs, idx)))
341 return;
342
343 if (!so->client || !(name = client_name(so->client)))
344 return;
345 }
346
347 if ((r = pa_hashmap_get(u->hashmap, name))) {
348 pa_xfree(name);
349
350 if (si) {
351
352 if (!r->volume_is_set || !pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
353 pa_log_info("Saving volume for <%s>", r->name);
354 r->volume = *pa_sink_input_get_volume(si);
355 r->volume_is_set = TRUE;
356 u->modified = TRUE;
357 }
358
359 if (!r->sink || strcmp(si->sink->name, r->sink) != 0) {
360 pa_log_info("Saving sink for <%s>", r->name);
361 pa_xfree(r->sink);
362 r->sink = pa_xstrdup(si->sink->name);
363 u->modified = TRUE;
364 }
365 } else {
366 pa_assert(so);
367
368 if (!r->source || strcmp(so->source->name, r->source) != 0) {
369 pa_log_info("Saving source for <%s>", r->name);
370 pa_xfree(r->source);
371 r->source = pa_xstrdup(so->source->name);
372 u->modified = TRUE;
373 }
374 }
375
376 } else {
377 pa_log_info("Creating new entry for <%s>", name);
378
379 r = pa_xnew(struct rule, 1);
380 r->name = name;
381
382 if (si) {
383 r->volume = *pa_sink_input_get_volume(si);
384 r->volume_is_set = TRUE;
385 r->sink = pa_xstrdup(si->sink->name);
386 r->source = NULL;
387 } else {
388 pa_assert(so);
389 r->volume_is_set = FALSE;
390 r->sink = NULL;
391 r->source = pa_xstrdup(so->source->name);
392 }
393
394 pa_hashmap_put(u->hashmap, r->name, r);
395 u->modified = TRUE;
396 }
397
398 if (u->modified && !u->save_time_event) {
399 struct timeval tv;
400 pa_gettimeofday(&tv);
401 tv.tv_sec += SAVE_INTERVAL;
402 u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u);
403 }
404 }
405
406 static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
407 struct rule *r;
408 char *name;
409
410 pa_assert(data);
411
412 /* In the NEW hook we only adjust the device. Adjusting the volume
413 * is left for the FIXATE hook */
414
415 if (!data->client || !(name = client_name(data->client)))
416 return PA_HOOK_OK;
417
418 if ((r = pa_hashmap_get(u->hashmap, name))) {
419 if (!data->sink && r->sink) {
420 if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK)))
421 pa_log_info("Restoring sink for <%s>", r->name);
422 }
423 }
424
425 pa_xfree(name);
426
427 return PA_HOOK_OK;
428 }
429
430 static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
431 struct rule *r;
432 char *name;
433
434 pa_assert(data);
435
436 /* In the FIXATE hook we only adjust the volum. Adjusting the device
437 * is left for the NEW hook */
438
439 if (!data->client || !(name = client_name(data->client)))
440 return PA_HOOK_OK;
441
442 if ((r = pa_hashmap_get(u->hashmap, name))) {
443
444 if (r->volume_is_set && data->sample_spec.channels == r->volume.channels) {
445 pa_log_info("Restoring volume for <%s>", r->name);
446 pa_sink_input_new_data_set_volume(data, &r->volume);
447 }
448 }
449
450 pa_xfree(name);
451
452 return PA_HOOK_OK;
453 }
454
455 static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) {
456 struct rule *r;
457 char *name;
458
459 pa_assert(data);
460
461 if (!data->client || !(name = client_name(data->client)))
462 return PA_HOOK_OK;
463
464 if ((r = pa_hashmap_get(u->hashmap, name))) {
465 if (!data->source && r->source) {
466 if ((data->source = pa_namereg_get(c, r->source, PA_NAMEREG_SOURCE)))
467 pa_log_info("Restoring source for <%s>", r->name);
468 }
469 }
470
471 return PA_HOOK_OK;
472 }
473
474 int pa__init(pa_module*m) {
475 pa_modargs *ma = NULL;
476 struct userdata *u;
477 pa_bool_t restore_device = TRUE, restore_volume = TRUE;
478
479 pa_assert(m);
480
481 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
482 pa_log("Failed to parse module arguments");
483 goto fail;
484 }
485
486 u = pa_xnew(struct userdata, 1);
487 u->core = m->core;
488 u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
489 u->modified = FALSE;
490 u->subscription = NULL;
491 u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL;
492 u->save_time_event = NULL;
493
494 m->userdata = u;
495
496 if (!(u->table_file = pa_state_path(pa_modargs_get_value(ma, "table", DEFAULT_VOLUME_TABLE_FILE), TRUE)))
497 goto fail;
498
499 if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 ||
500 pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) {
501 pa_log("restore_volume= and restore_device= expect boolean arguments");
502 goto fail;
503 }
504
505 if (!(restore_device || restore_volume)) {
506 pa_log("Both restrong the volume and restoring the device are disabled. There's no point in using this module at all then, failing.");
507 goto fail;
508 }
509
510 if (load_rules(u) < 0)
511 goto fail;
512
513 u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
514
515 if (restore_device) {
516 u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u);
517 u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_hook_callback, u);
518 }
519
520 if (restore_volume)
521 u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u);
522
523 pa_modargs_free(ma);
524 return 0;
525
526 fail:
527 pa__done(m);
528 if (ma)
529 pa_modargs_free(ma);
530
531 return -1;
532 }
533
534 static void free_func(void *p, void *userdata) {
535 struct rule *r = p;
536 pa_assert(r);
537
538 pa_xfree(r->name);
539 pa_xfree(r->sink);
540 pa_xfree(r->source);
541 pa_xfree(r);
542 }
543
544 void pa__done(pa_module*m) {
545 struct userdata* u;
546
547 pa_assert(m);
548
549 if (!(u = m->userdata))
550 return;
551
552 if (u->subscription)
553 pa_subscription_free(u->subscription);
554
555 if (u->sink_input_new_hook_slot)
556 pa_hook_slot_free(u->sink_input_new_hook_slot);
557 if (u->sink_input_fixate_hook_slot)
558 pa_hook_slot_free(u->sink_input_fixate_hook_slot);
559 if (u->source_output_new_hook_slot)
560 pa_hook_slot_free(u->source_output_new_hook_slot);
561
562 if (u->hashmap) {
563 save_rules(u);
564 pa_hashmap_free(u->hashmap, free_func, NULL);
565 }
566
567 if (u->save_time_event)
568 u->core->mainloop->time_free(u->save_time_event);
569
570 pa_xfree(u->table_file);
571 pa_xfree(u);
572 }