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_subscription
*subscription
;
66 pa_hook_slot
*card_new_hook_slot
;
67 pa_time_event
*save_time_event
;
68 pa_database
*database
;
71 #define ENTRY_VERSION 1
78 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
79 struct userdata
*u
= userdata
;
85 pa_assert(e
== u
->save_time_event
);
86 u
->core
->mainloop
->time_free(u
->save_time_event
);
87 u
->save_time_event
= NULL
;
89 pa_database_sync(u
->database
);
90 pa_log_info("Synced.");
93 static void trigger_save(struct userdata
*u
) {
94 if (u
->save_time_event
)
97 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
100 static struct entry
* entry_new(void) {
101 struct entry
*r
= pa_xnew0(struct entry
, 1);
102 r
->version
= ENTRY_VERSION
;
106 static void entry_free(struct entry
* e
) {
109 pa_xfree(e
->profile
);
113 static pa_bool_t
entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
122 t
= pa_tagstruct_new(NULL
, 0);
123 pa_tagstruct_putu8(t
, e
->version
);
124 pa_tagstruct_puts(t
, e
->profile
);
126 key
.data
= (char *) name
;
127 key
.size
= strlen(name
);
129 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
131 r
= (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0);
133 pa_tagstruct_free(t
);
138 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
140 #define LEGACY_ENTRY_VERSION 1
141 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
142 struct legacy_entry
{
144 char profile
[PA_NAME_MAX
];
146 struct legacy_entry
*le
;
152 if (data
->size
!= sizeof(struct legacy_entry
)) {
153 pa_log_debug("Size does not match.");
157 le
= (struct legacy_entry
*)data
->data
;
159 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
160 pa_log_debug("Version mismatch.");
164 if (!memchr(le
->profile
, 0, sizeof(le
->profile
))) {
165 pa_log_warn("Profile has missing NUL byte.");
170 e
->profile
= pa_xstrdup(le
->profile
);
175 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
177 struct entry
*e
= NULL
;
178 pa_tagstruct
*t
= NULL
;
184 key
.data
= (char*) name
;
185 key
.size
= strlen(name
);
189 if (!pa_database_get(u
->database
, &key
, &data
))
192 t
= pa_tagstruct_new(data
.data
, data
.size
);
195 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
196 e
->version
> ENTRY_VERSION
||
197 pa_tagstruct_gets(t
, &profile
) < 0) {
202 e
->profile
= pa_xstrdup(profile
);
204 if (!pa_tagstruct_eof(t
))
207 pa_tagstruct_free(t
);
208 pa_datum_free(&data
);
214 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
219 pa_tagstruct_free(t
);
221 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
222 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
223 if ((e
= legacy_entry_read(u
, &data
))) {
224 pa_log_debug("Success. Saving new format for key: %s", name
);
225 if (entry_write(u
, name
, e
))
227 pa_datum_free(&data
);
230 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
233 pa_datum_free(&data
);
237 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
238 struct userdata
*u
= userdata
;
239 struct entry
*entry
, *old
;
245 if (t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
) &&
246 t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_CHANGE
))
251 if (!(card
= pa_idxset_get_by_index(c
->cards
, idx
)))
254 if (!card
->save_profile
)
257 entry
->profile
= pa_xstrdup(card
->active_profile
? card
->active_profile
->name
: "");
259 if ((old
= entry_read(u
, card
->name
))) {
261 if (pa_streq(old
->profile
, entry
->profile
)) {
270 pa_log_info("Storing profile for card %s.", card
->name
);
272 if (entry_write(u
, card
->name
, entry
))
278 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
283 if ((e
= entry_read(u
, new_data
->name
)) && e
->profile
[0]) {
285 if (!new_data
->active_profile
) {
286 pa_log_info("Restoring profile for card %s.", new_data
->name
);
287 pa_card_new_data_set_profile(new_data
, e
->profile
);
288 new_data
->save_profile
= TRUE
;
290 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
298 int pa__init(pa_module
*m
) {
299 pa_modargs
*ma
= NULL
;
307 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
308 pa_log("Failed to parse module arguments");
312 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
316 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_CARD
, subscribe_callback
, u
);
318 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
);
320 if (!(fname
= pa_state_path("card-database", TRUE
)))
323 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
324 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
329 pa_log_info("Successfully opened database file '%s'.", fname
);
332 for (card
= pa_idxset_first(m
->core
->cards
, &idx
); card
; card
= pa_idxset_next(m
->core
->cards
, &idx
))
333 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
, card
->index
, u
);
347 void pa__done(pa_module
*m
) {
352 if (!(u
= m
->userdata
))
356 pa_subscription_free(u
->subscription
);
358 if (u
->card_new_hook_slot
)
359 pa_hook_slot_free(u
->card_new_hook_slot
);
361 if (u
->save_time_event
)
362 u
->core
->mainloop
->time_free(u
->save_time_event
);
365 pa_database_close(u
->database
);