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>
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
;
71 #define ENTRY_VERSION 1
75 char profile
[PA_NAME_MAX
];
78 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*tv
, void *userdata
) {
79 struct userdata
*u
= userdata
;
86 pa_assert(e
== u
->save_time_event
);
87 u
->core
->mainloop
->time_free(u
->save_time_event
);
88 u
->save_time_event
= NULL
;
90 gdbm_sync(u
->gdbm_file
);
91 pa_log_info("Synced.");
94 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
101 key
.dptr
= (char*) name
;
102 key
.dsize
= (int) strlen(name
);
104 data
= gdbm_fetch(u
->gdbm_file
, key
);
109 if (data
.dsize
!= sizeof(struct entry
)) {
110 pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name
, (unsigned long) data
.dsize
, (unsigned long) sizeof(struct entry
));
114 e
= (struct entry
*) data
.dptr
;
116 if (e
->version
!= ENTRY_VERSION
) {
117 pa_log_debug("Version of database entry for card %s doesn't match our version. Probably due to upgrade, ignoring.", name
);
121 if (!memchr(e
->profile
, 0, sizeof(e
->profile
))) {
122 pa_log_warn("Database contains entry for card %s with missing NUL byte in profile name", name
);
134 static void trigger_save(struct userdata
*u
) {
137 if (u
->save_time_event
)
140 pa_gettimeofday(&tv
);
141 tv
.tv_sec
+= SAVE_INTERVAL
;
142 u
->save_time_event
= u
->core
->mainloop
->time_new(u
->core
->mainloop
, &tv
, save_time_callback
, u
);
145 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
146 struct userdata
*u
= userdata
;
147 struct entry entry
, *old
;
154 if (t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
) &&
155 t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_CHANGE
))
158 memset(&entry
, 0, sizeof(entry
));
159 entry
.version
= ENTRY_VERSION
;
161 if (!(card
= pa_idxset_get_by_index(c
->cards
, idx
)))
164 pa_strlcpy(entry
.profile
, card
->active_profile
? card
->active_profile
->name
: "", sizeof(entry
.profile
));
166 if ((old
= read_entry(u
, card
->name
))) {
168 if (strncmp(old
->profile
, entry
.profile
, sizeof(entry
.profile
)) == 0) {
176 key
.dptr
= card
->name
;
177 key
.dsize
= (int) strlen(card
->name
);
179 data
.dptr
= (void*) &entry
;
180 data
.dsize
= sizeof(entry
);
182 pa_log_info("Storing profile for card %s.", card
->name
);
184 gdbm_store(u
->gdbm_file
, key
, data
, GDBM_REPLACE
);
189 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
194 if ((e
= read_entry(u
, new_data
->name
)) && e
->profile
[0]) {
196 if (!new_data
->active_profile
) {
197 pa_card_new_data_set_profile(new_data
, e
->profile
);
198 pa_log_info("Restoring profile for card %s.", new_data
->name
);
200 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
208 int pa__init(pa_module
*m
) {
209 pa_modargs
*ma
= NULL
;
218 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
219 pa_log("Failed to parse module arguments");
223 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
226 u
->save_time_event
= NULL
;
229 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_CARD
, subscribe_callback
, u
);
231 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
);
233 /* We include the host identifier in the file name because gdbm
234 * files are CPU dependant, and we don't want things to go wrong
235 * if we are on a multiarch system. */
237 fn
= pa_sprintf_malloc("card-database."CANONICAL_HOST
".gdbm");
238 fname
= pa_state_path(fn
, TRUE
);
244 if (!(u
->gdbm_file
= gdbm_open(fname
, 0, GDBM_WRCREAT
|GDBM_NOLOCK
, 0600, NULL
))) {
245 pa_log("Failed to open volume database '%s': %s", fname
, gdbm_strerror(gdbm_errno
));
250 /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */
251 gdbm_cache_size
= 10;
252 gdbm_setopt(u
->gdbm_file
, GDBM_CACHESIZE
, &gdbm_cache_size
, sizeof(gdbm_cache_size
));
254 pa_log_info("Sucessfully opened database file '%s'.", fname
);
257 for (card
= pa_idxset_first(m
->core
->cards
, &idx
); card
; card
= pa_idxset_next(m
->core
->cards
, &idx
))
258 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
, card
->index
, u
);
272 void pa__done(pa_module
*m
) {
277 if (!(u
= m
->userdata
))
281 pa_subscription_free(u
->subscription
);
283 if (u
->card_new_hook_slot
)
284 pa_hook_slot_free(u
->card_new_hook_slot
);
286 if (u
->save_time_event
)
287 u
->core
->mainloop
->time_free(u
->save_time_event
);
290 gdbm_close(u
->gdbm_file
);