2 This file is part of PulseAudio.
4 Copyright 2006-2008 Lennart Poettering
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.1 of the License,
9 or (at your option) any later version.
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.
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
29 #include <sys/types.h>
33 #include <pulse/gccmacro.h>
34 #include <pulse/xmalloc.h>
35 #include <pulse/timeval.h>
36 #include <pulse/rtclock.h>
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/card.h>
45 #include <pulsecore/namereg.h>
46 #include <pulsecore/database.h>
47 #include <pulsecore/tagstruct.h>
49 #include "module-card-restore-symdef.h"
51 PA_MODULE_AUTHOR("Lennart Poettering");
52 PA_MODULE_DESCRIPTION("Automatically restore profile of cards");
53 PA_MODULE_VERSION(PACKAGE_VERSION
);
54 PA_MODULE_LOAD_ONCE(true);
56 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
58 static const char* const valid_modargs
[] = {
65 pa_hook_slot
*card_new_hook_slot
;
66 pa_hook_slot
*card_put_hook_slot
;
67 pa_hook_slot
*card_profile_changed_hook_slot
;
68 pa_hook_slot
*card_profile_added_hook_slot
;
69 pa_hook_slot
*port_offset_hook_slot
;
70 pa_time_event
*save_time_event
;
71 pa_database
*database
;
75 #define ENTRY_VERSION 2
85 pa_hashmap
*ports
; /* Port name -> struct port_info */
88 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
89 struct userdata
*u
= userdata
;
95 pa_assert(e
== u
->save_time_event
);
96 u
->core
->mainloop
->time_free(u
->save_time_event
);
97 u
->save_time_event
= NULL
;
99 pa_database_sync(u
->database
);
100 pa_log_info("Synced.");
103 static void trigger_save(struct userdata
*u
) {
104 if (u
->save_time_event
)
107 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
110 static void port_info_free(struct port_info
*p_info
) {
113 pa_xfree(p_info
->name
);
117 static struct entry
* entry_new(void) {
118 struct entry
*r
= pa_xnew0(struct entry
, 1);
119 r
->version
= ENTRY_VERSION
;
120 r
->ports
= pa_hashmap_new_full(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
, NULL
, (pa_free_cb_t
) port_info_free
);
124 static struct port_info
*port_info_new(pa_device_port
*port
) {
125 struct port_info
*p_info
;
128 p_info
= pa_xnew(struct port_info
, 1);
129 p_info
->name
= pa_xstrdup(port
->name
);
130 p_info
->offset
= port
->latency_offset
;
132 p_info
= pa_xnew0(struct port_info
, 1);
137 static void entry_free(struct entry
* e
) {
140 pa_xfree(e
->profile
);
141 pa_hashmap_free(e
->ports
);
146 static struct entry
*entry_from_card(pa_card
*card
) {
147 struct port_info
*p_info
;
149 pa_device_port
*port
;
155 if (card
->save_profile
)
156 entry
->profile
= pa_xstrdup(card
->active_profile
->name
);
158 PA_HASHMAP_FOREACH(port
, card
->ports
, state
) {
159 p_info
= port_info_new(port
);
160 pa_assert_se(pa_hashmap_put(entry
->ports
, p_info
->name
, p_info
) >= 0);
166 static bool entrys_equal(struct entry
*a
, struct entry
*b
) {
167 struct port_info
*Ap_info
, *Bp_info
;
173 if (!pa_streq(a
->profile
, b
->profile
) ||
174 pa_hashmap_size(a
->ports
) != pa_hashmap_size(b
->ports
))
177 PA_HASHMAP_FOREACH(Ap_info
, a
->ports
, state
) {
178 if ((Bp_info
= pa_hashmap_get(b
->ports
, Ap_info
->name
))) {
179 if (Ap_info
->offset
!= Bp_info
->offset
)
188 static bool entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
193 struct port_info
*p_info
;
199 t
= pa_tagstruct_new(NULL
, 0);
200 pa_tagstruct_putu8(t
, e
->version
);
201 pa_tagstruct_puts(t
, e
->profile
);
202 pa_tagstruct_putu32(t
, pa_hashmap_size(e
->ports
));
204 PA_HASHMAP_FOREACH(p_info
, e
->ports
, state
) {
205 pa_tagstruct_puts(t
, p_info
->name
);
206 pa_tagstruct_puts64(t
, p_info
->offset
);
209 key
.data
= (char *) name
;
210 key
.size
= strlen(name
);
212 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
214 r
= (pa_database_set(u
->database
, &key
, &data
, true) == 0);
216 pa_tagstruct_free(t
);
221 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
223 #define LEGACY_ENTRY_VERSION 1
224 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
225 struct legacy_entry
{
227 char profile
[PA_NAME_MAX
];
229 struct legacy_entry
*le
;
235 if (data
->size
!= sizeof(struct legacy_entry
)) {
236 pa_log_debug("Size does not match.");
240 le
= (struct legacy_entry
*)data
->data
;
242 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
243 pa_log_debug("Version mismatch.");
247 if (!memchr(le
->profile
, 0, sizeof(le
->profile
))) {
248 pa_log_warn("Profile has missing NUL byte.");
253 e
->profile
= pa_xstrdup(le
->profile
);
258 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
260 struct entry
*e
= NULL
;
261 pa_tagstruct
*t
= NULL
;
267 key
.data
= (char*) name
;
268 key
.size
= strlen(name
);
272 if (!pa_database_get(u
->database
, &key
, &data
))
275 t
= pa_tagstruct_new(data
.data
, data
.size
);
278 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
279 e
->version
> ENTRY_VERSION
||
280 pa_tagstruct_gets(t
, &profile
) < 0) {
288 e
->profile
= pa_xstrdup(profile
);
290 if (e
->version
>= 2) {
291 uint32_t port_count
= 0;
292 const char *port_name
= NULL
;
293 int64_t port_offset
= 0;
294 struct port_info
*p_info
;
297 if (pa_tagstruct_getu32(t
, &port_count
) < 0)
300 for (i
= 0; i
< port_count
; i
++) {
301 if (pa_tagstruct_gets(t
, &port_name
) < 0 ||
303 pa_hashmap_get(e
->ports
, port_name
) ||
304 pa_tagstruct_gets64(t
, &port_offset
) < 0)
307 p_info
= port_info_new(NULL
);
308 p_info
->name
= pa_xstrdup(port_name
);
309 p_info
->offset
= port_offset
;
311 pa_assert_se(pa_hashmap_put(e
->ports
, p_info
->name
, p_info
) >= 0);
315 if (!pa_tagstruct_eof(t
))
318 pa_tagstruct_free(t
);
319 pa_datum_free(&data
);
325 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
330 pa_tagstruct_free(t
);
332 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
333 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
334 if ((e
= legacy_entry_read(u
, &data
))) {
335 pa_log_debug("Success. Saving new format for key: %s", name
);
336 if (entry_write(u
, name
, e
))
338 pa_datum_free(&data
);
341 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
344 pa_datum_free(&data
);
348 static void show_full_info(pa_card
*card
) {
351 if (card
->save_profile
)
352 pa_log_info("Storing profile and port latency offsets for card %s.", card
->name
);
354 pa_log_info("Storing port latency offsets for card %s.", card
->name
);
357 static pa_hook_result_t
card_put_hook_callback(pa_core
*c
, pa_card
*card
, struct userdata
*u
) {
358 struct entry
*entry
, *old
;
362 entry
= entry_from_card(card
);
364 if ((old
= entry_read(u
, card
->name
))) {
365 if (!card
->save_profile
)
366 entry
->profile
= pa_xstrdup(old
->profile
);
367 if (entrys_equal(entry
, old
))
371 show_full_info(card
);
373 if (entry_write(u
, card
->name
, entry
))
384 static pa_hook_result_t
card_profile_changed_callback(pa_core
*c
, pa_card
*card
, struct userdata
*u
) {
389 if (!card
->save_profile
)
392 if ((entry
= entry_read(u
, card
->name
))) {
393 pa_xfree(entry
->profile
);
394 entry
->profile
= pa_xstrdup(card
->active_profile
->name
);
395 pa_log_info("Storing card profile for card %s.", card
->name
);
397 entry
= entry_from_card(card
);
398 show_full_info(card
);
401 if (entry_write(u
, card
->name
, entry
))
408 static pa_hook_result_t
card_profile_added_callback(pa_core
*c
, pa_card_profile
*profile
, struct userdata
*u
) {
413 if (!(entry
= entry_read(u
, profile
->card
->name
)))
416 if (pa_safe_streq(entry
->profile
, profile
->name
)) {
417 if (pa_card_set_profile(profile
->card
, profile
, true) >= 0)
418 pa_log_info("Restored profile '%s' for card %s.", profile
->name
, profile
->card
->name
);
426 static pa_hook_result_t
port_offset_change_callback(pa_core
*c
, pa_device_port
*port
, struct userdata
*u
) {
433 if ((entry
= entry_read(u
, card
->name
))) {
434 struct port_info
*p_info
;
436 if ((p_info
= pa_hashmap_get(entry
->ports
, port
->name
)))
437 p_info
->offset
= port
->latency_offset
;
439 p_info
= port_info_new(port
);
440 pa_assert_se(pa_hashmap_put(entry
->ports
, p_info
->name
, p_info
) >= 0);
443 pa_log_info("Storing latency offset for port %s on card %s.", port
->name
, card
->name
);
446 entry
= entry_from_card(card
);
447 show_full_info(card
);
450 if (entry_write(u
, card
->name
, entry
))
457 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
461 struct port_info
*p_info
;
465 if (!(e
= entry_read(u
, new_data
->name
)))
469 if (!new_data
->active_profile
) {
470 pa_card_new_data_set_profile(new_data
, e
->profile
);
471 pa_log_info("Restored profile '%s' for card %s.", new_data
->active_profile
, new_data
->name
);
472 new_data
->save_profile
= true;
475 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
478 /* Always restore the latency offsets because their
479 * initial value is always 0 */
481 pa_log_info("Restoring port latency offsets for card %s.", new_data
->name
);
483 PA_HASHMAP_FOREACH(p_info
, e
->ports
, state
)
484 if ((p
= pa_hashmap_get(new_data
->ports
, p_info
->name
)))
485 p
->latency_offset
= p_info
->offset
;
492 int pa__init(pa_module
*m
) {
493 pa_modargs
*ma
= NULL
;
499 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
500 pa_log("Failed to parse module arguments");
504 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
508 u
->card_new_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_CARD_NEW
], PA_HOOK_EARLY
, (pa_hook_cb_t
) card_new_hook_callback
, u
);
509 u
->card_put_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_CARD_PUT
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) card_put_hook_callback
, u
);
510 u
->card_profile_changed_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_CARD_PROFILE_CHANGED
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) card_profile_changed_callback
, u
);
511 u
->card_profile_added_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_CARD_PROFILE_ADDED
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) card_profile_added_callback
, u
);
512 u
->port_offset_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) port_offset_change_callback
, u
);
513 u
->hooks_connected
= true;
515 if (!(fname
= pa_state_path("card-database", true)))
518 if (!(u
->database
= pa_database_open(fname
, true))) {
519 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
524 pa_log_info("Successfully opened database file '%s'.", fname
);
539 void pa__done(pa_module
*m
) {
544 if (!(u
= m
->userdata
))
547 if (u
->hooks_connected
) {
548 pa_hook_slot_free(u
->card_new_hook_slot
);
549 pa_hook_slot_free(u
->card_put_hook_slot
);
550 pa_hook_slot_free(u
->card_profile_changed_hook_slot
);
551 pa_hook_slot_free(u
->card_profile_added_hook_slot
);
552 pa_hook_slot_free(u
->port_offset_hook_slot
);
555 if (u
->save_time_event
)
556 u
->core
->mainloop
->time_free(u
->save_time_event
);
559 pa_database_close(u
->database
);