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 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>
35 #include <pulse/xmalloc.h>
36 #include <pulse/volume.h>
37 #include <pulse/timeval.h>
38 #include <pulse/util.h>
40 #include <pulsecore/core-error.h>
41 #include <pulsecore/module.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/modargs.h>
44 #include <pulsecore/log.h>
45 #include <pulsecore/core-subscribe.h>
46 #include <pulsecore/card.h>
47 #include <pulsecore/namereg.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
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
;
72 char profile
[PA_NAME_MAX
];
75 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*tv
, void *userdata
) {
76 struct userdata
*u
= userdata
;
83 pa_assert(e
== u
->save_time_event
);
84 u
->core
->mainloop
->time_free(u
->save_time_event
);
85 u
->save_time_event
= NULL
;
87 gdbm_sync(u
->gdbm_file
);
88 pa_log_info("Synced.");
91 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
98 key
.dptr
= (char*) name
;
99 key
.dsize
= (int) strlen(name
);
101 data
= gdbm_fetch(u
->gdbm_file
, key
);
106 if (data
.dsize
!= sizeof(struct entry
)) {
107 pa_log_warn("Database contains entry for card %s of wrong size %lu != %lu", name
, (unsigned long) data
.dsize
, (unsigned long) sizeof(struct entry
));
111 e
= (struct entry
*) data
.dptr
;
113 if (!memchr(e
->profile
, 0, sizeof(e
->profile
))) {
114 pa_log_warn("Database contains entry for card %s with missing NUL byte in profile name", name
);
126 static void trigger_save(struct userdata
*u
) {
129 if (u
->save_time_event
)
132 pa_gettimeofday(&tv
);
133 tv
.tv_sec
+= SAVE_INTERVAL
;
134 u
->save_time_event
= u
->core
->mainloop
->time_new(u
->core
->mainloop
, &tv
, save_time_callback
, u
);
137 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
138 struct userdata
*u
= userdata
;
139 struct entry entry
, *old
;
146 if (t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
) &&
147 t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_CHANGE
))
150 memset(&entry
, 0, sizeof(entry
));
152 if (!(card
= pa_idxset_get_by_index(c
->cards
, idx
)))
155 pa_strlcpy(entry
.profile
, card
->active_profile
? card
->active_profile
->name
: "", sizeof(entry
.profile
));
157 if ((old
= read_entry(u
, card
->name
))) {
159 if (strncmp(old
->profile
, entry
.profile
, sizeof(entry
.profile
)) == 0) {
167 key
.dptr
= card
->name
;
168 key
.dsize
= (int) strlen(card
->name
);
170 data
.dptr
= (void*) &entry
;
171 data
.dsize
= sizeof(entry
);
173 pa_log_info("Storing profile for card %s.", card
->name
);
175 gdbm_store(u
->gdbm_file
, key
, data
, GDBM_REPLACE
);
180 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
185 if ((e
= read_entry(u
, new_data
->name
)) && e
->profile
) {
187 if (!new_data
->active_profile
) {
188 pa_card_new_data_set_profile(new_data
, e
->profile
);
189 pa_log_info("Restoring profile for card %s.", new_data
->name
);
191 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
199 int pa__init(pa_module
*m
) {
200 pa_modargs
*ma
= NULL
;
209 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
210 pa_log("Failed to parse module arguments");
214 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
217 u
->save_time_event
= NULL
;
220 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_CARD
, subscribe_callback
, u
);
222 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
);
224 /* We include the host identifier in the file name because gdbm
225 * files are CPU dependant, and we don't want things to go wrong
226 * if we are on a multiarch system. */
228 fn
= pa_sprintf_malloc("card-database."CANONICAL_HOST
".gdbm");
229 fname
= pa_state_path(fn
, TRUE
);
235 if (!(u
->gdbm_file
= gdbm_open(fname
, 0, GDBM_WRCREAT
|GDBM_NOLOCK
, 0600, NULL
))) {
236 pa_log("Failed to open volume database '%s': %s", fname
, gdbm_strerror(gdbm_errno
));
241 /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */
242 gdbm_cache_size
= 10;
243 gdbm_setopt(u
->gdbm_file
, GDBM_CACHESIZE
, &gdbm_cache_size
, sizeof(gdbm_cache_size
));
245 pa_log_info("Sucessfully opened database file '%s'.", fname
);
248 for (card
= pa_idxset_first(m
->core
->cards
, &idx
); card
; card
= pa_idxset_next(m
->core
->cards
, &idx
))
249 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
, card
->index
, u
);
263 void pa__done(pa_module
*m
) {
268 if (!(u
= m
->userdata
))
272 pa_subscription_free(u
->subscription
);
274 if (u
->card_new_hook_slot
)
275 pa_hook_slot_free(u
->card_new_hook_slot
);
277 if (u
->save_time_event
)
278 u
->core
->mainloop
->time_free(u
->save_time_event
);
281 gdbm_close(u
->gdbm_file
);