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_hook_slot
;
68 pa_hook_slot
*port_offset_hook_slot
;
69 pa_time_event
*save_time_event
;
70 pa_database
*database
;
74 #define ENTRY_VERSION 2
84 pa_hashmap
*ports
; /* Port name -> struct port_info */
87 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
88 struct userdata
*u
= userdata
;
94 pa_assert(e
== u
->save_time_event
);
95 u
->core
->mainloop
->time_free(u
->save_time_event
);
96 u
->save_time_event
= NULL
;
98 pa_database_sync(u
->database
);
99 pa_log_info("Synced.");
102 static void trigger_save(struct userdata
*u
) {
103 if (u
->save_time_event
)
106 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
109 static struct entry
* entry_new(void) {
110 struct entry
*r
= pa_xnew0(struct entry
, 1);
111 r
->version
= ENTRY_VERSION
;
112 r
->ports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
116 static struct port_info
*port_info_new(pa_device_port
*port
) {
117 struct port_info
*p_info
;
120 p_info
= pa_xnew(struct port_info
, 1);
121 p_info
->name
= pa_xstrdup(port
->name
);
122 p_info
->offset
= port
->latency_offset
;
124 p_info
= pa_xnew0(struct port_info
, 1);
129 static void port_info_free(struct port_info
*p_info
) {
132 pa_xfree(p_info
->name
);
136 static void entry_free(struct entry
* e
) {
139 pa_xfree(e
->profile
);
140 pa_hashmap_free(e
->ports
, (pa_free_cb_t
) port_info_free
);
145 static struct entry
*entry_from_card(pa_card
*card
) {
146 struct port_info
*p_info
;
148 pa_device_port
*port
;
154 if (card
->save_profile
)
155 entry
->profile
= pa_xstrdup(card
->active_profile
->name
);
157 PA_HASHMAP_FOREACH(port
, card
->ports
, state
) {
158 p_info
= port_info_new(port
);
159 pa_assert_se(pa_hashmap_put(entry
->ports
, p_info
->name
, p_info
) >= 0);
165 static bool entrys_equal(struct entry
*a
, struct entry
*b
) {
166 struct port_info
*Ap_info
, *Bp_info
;
172 if (!pa_streq(a
->profile
, b
->profile
) ||
173 pa_hashmap_size(a
->ports
) != pa_hashmap_size(b
->ports
))
176 PA_HASHMAP_FOREACH(Ap_info
, a
->ports
, state
) {
177 if ((Bp_info
= pa_hashmap_get(b
->ports
, Ap_info
->name
))) {
178 if (Ap_info
->offset
!= Bp_info
->offset
)
187 static bool entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
192 struct port_info
*p_info
;
198 t
= pa_tagstruct_new(NULL
, 0);
199 pa_tagstruct_putu8(t
, e
->version
);
200 pa_tagstruct_puts(t
, e
->profile
);
201 pa_tagstruct_putu32(t
, pa_hashmap_size(e
->ports
));
203 PA_HASHMAP_FOREACH(p_info
, e
->ports
, state
) {
204 pa_tagstruct_puts(t
, p_info
->name
);
205 pa_tagstruct_puts64(t
, p_info
->offset
);
208 key
.data
= (char *) name
;
209 key
.size
= strlen(name
);
211 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
213 r
= (pa_database_set(u
->database
, &key
, &data
, true) == 0);
215 pa_tagstruct_free(t
);
220 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
222 #define LEGACY_ENTRY_VERSION 1
223 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
224 struct legacy_entry
{
226 char profile
[PA_NAME_MAX
];
228 struct legacy_entry
*le
;
234 if (data
->size
!= sizeof(struct legacy_entry
)) {
235 pa_log_debug("Size does not match.");
239 le
= (struct legacy_entry
*)data
->data
;
241 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
242 pa_log_debug("Version mismatch.");
246 if (!memchr(le
->profile
, 0, sizeof(le
->profile
))) {
247 pa_log_warn("Profile has missing NUL byte.");
252 e
->profile
= pa_xstrdup(le
->profile
);
257 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
259 struct entry
*e
= NULL
;
260 pa_tagstruct
*t
= NULL
;
266 key
.data
= (char*) name
;
267 key
.size
= strlen(name
);
271 if (!pa_database_get(u
->database
, &key
, &data
))
274 t
= pa_tagstruct_new(data
.data
, data
.size
);
277 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
278 e
->version
> ENTRY_VERSION
||
279 pa_tagstruct_gets(t
, &profile
) < 0) {
287 e
->profile
= pa_xstrdup(profile
);
289 if (e
->version
>= 2) {
290 uint32_t port_count
= 0;
291 const char *port_name
= NULL
;
292 int64_t port_offset
= 0;
293 struct port_info
*p_info
;
296 if (pa_tagstruct_getu32(t
, &port_count
) < 0)
299 for (i
= 0; i
< port_count
; i
++) {
300 if (pa_tagstruct_gets(t
, &port_name
) < 0 ||
302 pa_hashmap_get(e
->ports
, port_name
) ||
303 pa_tagstruct_gets64(t
, &port_offset
) < 0)
306 p_info
= port_info_new(NULL
);
307 p_info
->name
= pa_xstrdup(port_name
);
308 p_info
->offset
= port_offset
;
310 pa_assert_se(pa_hashmap_put(e
->ports
, p_info
->name
, p_info
) >= 0);
314 if (!pa_tagstruct_eof(t
))
317 pa_tagstruct_free(t
);
318 pa_datum_free(&data
);
324 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
329 pa_tagstruct_free(t
);
331 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
332 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
333 if ((e
= legacy_entry_read(u
, &data
))) {
334 pa_log_debug("Success. Saving new format for key: %s", name
);
335 if (entry_write(u
, name
, e
))
337 pa_datum_free(&data
);
340 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
343 pa_datum_free(&data
);
347 static void show_full_info(pa_card
*card
) {
350 if (card
->save_profile
)
351 pa_log_info("Storing profile and port latency offsets for card %s.", card
->name
);
353 pa_log_info("Storing port latency offsets for card %s.", card
->name
);
356 static pa_hook_result_t
card_put_hook_callback(pa_core
*c
, pa_card
*card
, struct userdata
*u
) {
357 struct entry
*entry
, *old
;
361 entry
= entry_from_card(card
);
363 if ((old
= entry_read(u
, card
->name
))) {
364 if (!card
->save_profile
)
365 entry
->profile
= pa_xstrdup(old
->profile
);
366 if (entrys_equal(entry
, old
))
370 show_full_info(card
);
372 if (entry_write(u
, card
->name
, entry
))
383 static pa_hook_result_t
card_profile_change_callback(pa_core
*c
, pa_card
*card
, struct userdata
*u
) {
388 if (!card
->save_profile
)
391 if ((entry
= entry_read(u
, card
->name
))) {
392 entry
->profile
= pa_xstrdup(card
->active_profile
->name
);
393 pa_log_info("Storing card profile for card %s.", card
->name
);
395 entry
= entry_from_card(card
);
396 show_full_info(card
);
399 if (entry_write(u
, card
->name
, entry
))
406 static pa_hook_result_t
port_offset_change_callback(pa_core
*c
, pa_device_port
*port
, struct userdata
*u
) {
413 if ((entry
= entry_read(u
, card
->name
))) {
414 struct port_info
*p_info
;
416 if ((p_info
= pa_hashmap_get(entry
->ports
, port
->name
)))
417 p_info
->offset
= port
->latency_offset
;
419 p_info
= port_info_new(port
);
420 pa_assert_se(pa_hashmap_put(entry
->ports
, p_info
->name
, p_info
) >= 0);
423 pa_log_info("Storing latency offset for port %s on card %s.", port
->name
, card
->name
);
426 entry_from_card(card
);
427 show_full_info(card
);
430 if (entry_write(u
, card
->name
, entry
))
437 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
441 struct port_info
*p_info
;
445 if (!(e
= entry_read(u
, new_data
->name
)))
449 if (!new_data
->active_profile
) {
450 pa_card_new_data_set_profile(new_data
, e
->profile
);
451 pa_log_info("Restored profile '%s' for card %s.", new_data
->active_profile
, new_data
->name
);
452 new_data
->save_profile
= true;
455 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
458 /* Always restore the latency offsets because their
459 * initial value is always 0 */
461 pa_log_info("Restoring port latency offsets for card %s.", new_data
->name
);
463 PA_HASHMAP_FOREACH(p_info
, e
->ports
, state
)
464 if ((p
= pa_hashmap_get(new_data
->ports
, p_info
->name
)))
465 p
->latency_offset
= p_info
->offset
;
472 int pa__init(pa_module
*m
) {
473 pa_modargs
*ma
= NULL
;
479 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
480 pa_log("Failed to parse module arguments");
484 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
488 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
);
489 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
);
490 u
->card_profile_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_CARD_PROFILE_CHANGED
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) card_profile_change_callback
, u
);
491 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
);
492 u
->hooks_connected
= true;
494 if (!(fname
= pa_state_path("card-database", true)))
497 if (!(u
->database
= pa_database_open(fname
, true))) {
498 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
503 pa_log_info("Successfully opened database file '%s'.", fname
);
518 void pa__done(pa_module
*m
) {
523 if (!(u
= m
->userdata
))
526 if (u
->hooks_connected
) {
527 pa_hook_slot_free(u
->card_new_hook_slot
);
528 pa_hook_slot_free(u
->card_put_hook_slot
);
529 pa_hook_slot_free(u
->card_profile_hook_slot
);
530 pa_hook_slot_free(u
->port_offset_hook_slot
);
533 if (u
->save_time_event
)
534 u
->core
->mainloop
->time_free(u
->save_time_event
);
537 pa_database_close(u
->database
);