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>
34 #include <pulse/xmalloc.h>
35 #include <pulse/volume.h>
36 #include <pulse/timeval.h>
37 #include <pulse/util.h>
38 #include <pulse/rtclock.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>
48 #include <pulsecore/database.h>
49 #include <pulsecore/tagstruct.h>
51 #include "module-card-restore-symdef.h"
53 PA_MODULE_AUTHOR("Lennart Poettering");
54 PA_MODULE_DESCRIPTION("Automatically restore profile of cards");
55 PA_MODULE_VERSION(PACKAGE_VERSION
);
56 PA_MODULE_LOAD_ONCE(TRUE
);
58 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
60 static const char* const valid_modargs
[] = {
67 pa_subscription
*subscription
;
68 pa_hook_slot
*card_new_hook_slot
;
69 pa_time_event
*save_time_event
;
70 pa_database
*database
;
73 #define ENTRY_VERSION 1
80 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
81 struct userdata
*u
= userdata
;
87 pa_assert(e
== u
->save_time_event
);
88 u
->core
->mainloop
->time_free(u
->save_time_event
);
89 u
->save_time_event
= NULL
;
91 pa_database_sync(u
->database
);
92 pa_log_info("Synced.");
95 static void trigger_save(struct userdata
*u
) {
96 if (u
->save_time_event
)
99 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
102 static struct entry
* entry_new(void) {
103 struct entry
*r
= pa_xnew0(struct entry
, 1);
104 r
->version
= ENTRY_VERSION
;
108 static void entry_free(struct entry
* e
) {
111 pa_xfree(e
->profile
);
115 static pa_bool_t
entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
124 t
= pa_tagstruct_new(NULL
, 0);
125 pa_tagstruct_putu8(t
, e
->version
);
126 pa_tagstruct_puts(t
, e
->profile
);
128 key
.data
= (char *) name
;
129 key
.size
= strlen(name
);
131 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
133 r
= (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0);
135 pa_tagstruct_free(t
);
140 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
142 #define LEGACY_ENTRY_VERSION 1
143 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
144 struct legacy_entry
{
146 char profile
[PA_NAME_MAX
];
148 struct legacy_entry
*le
;
154 if (data
->size
!= sizeof(struct legacy_entry
)) {
155 pa_log_debug("Size does not match.");
159 le
= (struct legacy_entry
*)data
->data
;
161 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
162 pa_log_debug("Version mismatch.");
166 if (!memchr(le
->profile
, 0, sizeof(le
->profile
))) {
167 pa_log_warn("Profile has missing NUL byte.");
172 e
->profile
= pa_xstrdup(le
->profile
);
177 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
179 struct entry
*e
= NULL
;
180 pa_tagstruct
*t
= NULL
;
186 key
.data
= (char*) name
;
187 key
.size
= strlen(name
);
191 if (!pa_database_get(u
->database
, &key
, &data
))
194 t
= pa_tagstruct_new(data
.data
, data
.size
);
197 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
198 e
->version
> ENTRY_VERSION
||
199 pa_tagstruct_gets(t
, &profile
) < 0) {
204 e
->profile
= pa_xstrdup(profile
);
206 if (!pa_tagstruct_eof(t
))
209 pa_tagstruct_free(t
);
210 pa_datum_free(&data
);
216 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
221 pa_tagstruct_free(t
);
223 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
224 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
225 if ((e
= legacy_entry_read(u
, &data
))) {
226 pa_log_debug("Success. Saving new format for key: %s", name
);
227 if (entry_write(u
, name
, e
))
229 pa_datum_free(&data
);
232 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
235 pa_datum_free(&data
);
239 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
240 struct userdata
*u
= userdata
;
241 struct entry
*entry
, *old
;
247 if (t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
) &&
248 t
!= (PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_CHANGE
))
253 if (!(card
= pa_idxset_get_by_index(c
->cards
, idx
)))
256 if (!card
->save_profile
)
259 entry
->profile
= pa_xstrdup(card
->active_profile
? card
->active_profile
->name
: "");
261 if ((old
= entry_read(u
, card
->name
))) {
263 if (pa_streq(old
->profile
, entry
->profile
)) {
272 pa_log_info("Storing profile for card %s.", card
->name
);
274 if (entry_write(u
, card
->name
, entry
))
280 static pa_hook_result_t
card_new_hook_callback(pa_core
*c
, pa_card_new_data
*new_data
, struct userdata
*u
) {
285 if ((e
= entry_read(u
, new_data
->name
)) && e
->profile
[0]) {
287 if (!new_data
->active_profile
) {
288 pa_log_info("Restoring profile for card %s.", new_data
->name
);
289 pa_card_new_data_set_profile(new_data
, e
->profile
);
290 new_data
->save_profile
= TRUE
;
292 pa_log_debug("Not restoring profile for card %s, because already set.", new_data
->name
);
300 int pa__init(pa_module
*m
) {
301 pa_modargs
*ma
= NULL
;
309 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
310 pa_log("Failed to parse module arguments");
314 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
318 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_CARD
, subscribe_callback
, u
);
320 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
);
322 if (!(fname
= pa_state_path("card-database", TRUE
)))
325 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
326 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
331 pa_log_info("Successfully opened database file '%s'.", fname
);
334 for (card
= pa_idxset_first(m
->core
->cards
, &idx
); card
; card
= pa_idxset_next(m
->core
->cards
, &idx
))
335 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_CARD
|PA_SUBSCRIPTION_EVENT_NEW
, card
->index
, u
);
349 void pa__done(pa_module
*m
) {
354 if (!(u
= m
->userdata
))
358 pa_subscription_free(u
->subscription
);
360 if (u
->card_new_hook_slot
)
361 pa_hook_slot_free(u
->card_new_hook_slot
);
363 if (u
->save_time_event
)
364 u
->core
->mainloop
->time_free(u
->save_time_event
);
367 pa_database_close(u
->database
);