~diwic/ubuntu/lucid/pulseaudio/bugfixes

« back to all changes in this revision

Viewing changes to src/modules/module-card-restore.c

  • Committer: Bazaar Package Importer
  • Author(s): Luke Yelavich
  • Date: 2009-05-05 14:18:20 UTC
  • mfrom: (1.2.4 upstream) (1.1.8 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090505141820-rrr2mtdd1jkllvr8
Tags: 1:0.9.15-1ubuntu1
* Merge from unreleased Debian pulseaudio git, remaining changes:
  - epoch (my stupid fault :S)
  - Don't build against, and create jack package. Jack is not in main
  - use linear resampler to work better with lack of PREEMPT in jaunty's
    -generic kernel config, also change buffer size
  - Add alsa configuration files to route alsa applications via pulseaudio
  - Move libasound2-plugins from Recommends to Depends
  - Add pm-utils sleep hook to suspend (and resume) users' pulseaudio
    daemons
  - patch to fix source/sink and suspend-on-idle race
  - Make initscript more informative in the default case of per-user
    sessions
  - create /var/run/pulse, and make restart more robust
  - add status check for system wide pulseaudio instance
  - LSB {Required-*,Should-*} should specify hal instead of dbus,
    since hal is required (and already requires dbus)
  - indicate that the system pulseaudio instance is being started from the init
    script
  - Install more upstream man pages
  - Link to pacat for parec man page
  - check whether pulseaudio is running before preloading the padsp library
  - Add DEB_OPT_FLAG = -O3 as per recommendation from
    pulseaudio-discuss/2007-December/001017.html
  - cache /usr/share/sounds/ubuntu/stereo/ wav files on pulseaudio load
  - disable glitch free (use tsched=0)
  - Generate a PO template on build
  - add special case to disable pulseaudio loading if accessibility/speech
    is being used
  - the sd wrapper script should not load pulseaudio if pulseaudio is being
    used as a system service
  - add a pulseaudio apport hook
  - fix some typos in README.Debian
  - demote paprefs to suggests
  - drop padevchooser(Recommends) and pavucontrol (Suggests)
  - drop libasyncns-dev build dependency, its in universe
* add libudev-dev as a build-dependency

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***
 
2
  This file is part of PulseAudio.
 
3
 
 
4
  Copyright 2006-2008 Lennart Poettering
 
5
 
 
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.
 
10
 
 
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.
 
15
 
 
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
 
19
  USA.
 
20
***/
 
21
 
 
22
#ifdef HAVE_CONFIG_H
 
23
#include <config.h>
 
24
#endif
 
25
 
 
26
#include <unistd.h>
 
27
#include <string.h>
 
28
#include <errno.h>
 
29
#include <sys/types.h>
 
30
#include <stdio.h>
 
31
#include <stdlib.h>
 
32
#include <ctype.h>
 
33
#include <gdbm.h>
 
34
 
 
35
#include <pulse/xmalloc.h>
 
36
#include <pulse/volume.h>
 
37
#include <pulse/timeval.h>
 
38
#include <pulse/util.h>
 
39
 
 
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
 
 
49
#include "module-card-restore-symdef.h"
 
50
 
 
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);
 
55
 
 
56
#define SAVE_INTERVAL 10
 
57
 
 
58
static const char* const valid_modargs[] = {
 
59
    NULL
 
60
};
 
61
 
 
62
struct userdata {
 
63
    pa_core *core;
 
64
    pa_module *module;
 
65
    pa_subscription *subscription;
 
66
    pa_hook_slot *card_new_hook_slot;
 
67
    pa_time_event *save_time_event;
 
68
    GDBM_FILE gdbm_file;
 
69
};
 
70
 
 
71
#define ENTRY_VERSION 1
 
72
 
 
73
struct entry {
 
74
    uint8_t version;
 
75
    char profile[PA_NAME_MAX];
 
76
} PA_GCC_PACKED ;
 
77
 
 
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;
 
80
 
 
81
    pa_assert(a);
 
82
    pa_assert(e);
 
83
    pa_assert(tv);
 
84
    pa_assert(u);
 
85
 
 
86
    pa_assert(e == u->save_time_event);
 
87
    u->core->mainloop->time_free(u->save_time_event);
 
88
    u->save_time_event = NULL;
 
89
 
 
90
    gdbm_sync(u->gdbm_file);
 
91
    pa_log_info("Synced.");
 
92
}
 
93
 
 
94
static struct entry* read_entry(struct userdata *u, const char *name) {
 
95
    datum key, data;
 
96
    struct entry *e;
 
97
 
 
98
    pa_assert(u);
 
99
    pa_assert(name);
 
100
 
 
101
    key.dptr = (char*) name;
 
102
    key.dsize = (int) strlen(name);
 
103
 
 
104
    data = gdbm_fetch(u->gdbm_file, key);
 
105
 
 
106
    if (!data.dptr)
 
107
        goto fail;
 
108
 
 
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));
 
111
        goto fail;
 
112
    }
 
113
 
 
114
    e = (struct entry*) data.dptr;
 
115
 
 
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);
 
118
        goto fail;
 
119
    }
 
120
 
 
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);
 
123
        goto fail;
 
124
    }
 
125
 
 
126
    return e;
 
127
 
 
128
fail:
 
129
 
 
130
    pa_xfree(data.dptr);
 
131
    return NULL;
 
132
}
 
133
 
 
134
static void trigger_save(struct userdata *u) {
 
135
    struct timeval tv;
 
136
 
 
137
    if (u->save_time_event)
 
138
        return;
 
139
 
 
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);
 
143
}
 
144
 
 
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;
 
148
    datum key, data;
 
149
    pa_card *card;
 
150
 
 
151
    pa_assert(c);
 
152
    pa_assert(u);
 
153
 
 
154
    if (t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW) &&
 
155
        t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE))
 
156
        return;
 
157
 
 
158
    memset(&entry, 0, sizeof(entry));
 
159
    entry.version = ENTRY_VERSION;
 
160
 
 
161
    if (!(card = pa_idxset_get_by_index(c->cards, idx)))
 
162
        return;
 
163
 
 
164
    if (!card->save_profile)
 
165
        return;
 
166
 
 
167
    pa_strlcpy(entry.profile, card->active_profile ? card->active_profile->name : "", sizeof(entry.profile));
 
168
 
 
169
    if ((old = read_entry(u, card->name))) {
 
170
 
 
171
        if (strncmp(old->profile, entry.profile, sizeof(entry.profile)) == 0) {
 
172
            pa_xfree(old);
 
173
            return;
 
174
        }
 
175
 
 
176
        pa_xfree(old);
 
177
    }
 
178
 
 
179
    key.dptr = card->name;
 
180
    key.dsize = (int) strlen(card->name);
 
181
 
 
182
    data.dptr = (void*) &entry;
 
183
    data.dsize = sizeof(entry);
 
184
 
 
185
    pa_log_info("Storing profile for card %s.", card->name);
 
186
 
 
187
    gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE);
 
188
 
 
189
    trigger_save(u);
 
190
}
 
191
 
 
192
static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new_data, struct userdata *u) {
 
193
    struct entry *e;
 
194
 
 
195
    pa_assert(new_data);
 
196
 
 
197
    if ((e = read_entry(u, new_data->name)) && e->profile[0]) {
 
198
 
 
199
        if (!new_data->active_profile) {
 
200
            pa_card_new_data_set_profile(new_data, e->profile);
 
201
            pa_log_info("Restoring profile for card %s.", new_data->name);
 
202
        } else
 
203
            pa_log_debug("Not restoring profile for card %s, because already set.", new_data->name);
 
204
 
 
205
        pa_xfree(e);
 
206
    }
 
207
 
 
208
    return PA_HOOK_OK;
 
209
}
 
210
 
 
211
int pa__init(pa_module*m) {
 
212
    pa_modargs *ma = NULL;
 
213
    struct userdata *u;
 
214
    char *fname, *fn;
 
215
    pa_card *card;
 
216
    uint32_t idx;
 
217
    int gdbm_cache_size;
 
218
 
 
219
    pa_assert(m);
 
220
 
 
221
    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
 
222
        pa_log("Failed to parse module arguments");
 
223
        goto fail;
 
224
    }
 
225
 
 
226
    m->userdata = u = pa_xnew(struct userdata, 1);
 
227
    u->core = m->core;
 
228
    u->module = m;
 
229
    u->save_time_event = NULL;
 
230
    u->gdbm_file = NULL;
 
231
 
 
232
    u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CARD, subscribe_callback, u);
 
233
 
 
234
    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);
 
235
 
 
236
    /* We include the host identifier in the file name because gdbm
 
237
     * files are CPU dependant, and we don't want things to go wrong
 
238
     * if we are on a multiarch system. */
 
239
 
 
240
    fn = pa_sprintf_malloc("card-database."CANONICAL_HOST".gdbm");
 
241
    fname = pa_state_path(fn, TRUE);
 
242
    pa_xfree(fn);
 
243
 
 
244
    if (!fname)
 
245
        goto fail;
 
246
 
 
247
    if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) {
 
248
        pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno));
 
249
        pa_xfree(fname);
 
250
        goto fail;
 
251
    }
 
252
 
 
253
    /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */
 
254
    gdbm_cache_size = 10;
 
255
    gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size));
 
256
 
 
257
    pa_log_info("Sucessfully opened database file '%s'.", fname);
 
258
    pa_xfree(fname);
 
259
 
 
260
    for (card = pa_idxset_first(m->core->cards, &idx); card; card = pa_idxset_next(m->core->cards, &idx))
 
261
        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW, card->index, u);
 
262
 
 
263
    pa_modargs_free(ma);
 
264
    return 0;
 
265
 
 
266
fail:
 
267
    pa__done(m);
 
268
 
 
269
    if (ma)
 
270
        pa_modargs_free(ma);
 
271
 
 
272
    return  -1;
 
273
}
 
274
 
 
275
void pa__done(pa_module*m) {
 
276
    struct userdata* u;
 
277
 
 
278
    pa_assert(m);
 
279
 
 
280
    if (!(u = m->userdata))
 
281
        return;
 
282
 
 
283
    if (u->subscription)
 
284
        pa_subscription_free(u->subscription);
 
285
 
 
286
    if (u->card_new_hook_slot)
 
287
        pa_hook_slot_free(u->card_new_hook_slot);
 
288
 
 
289
    if (u->save_time_event)
 
290
        u->core->mainloop->time_free(u->save_time_event);
 
291
 
 
292
    if (u->gdbm_file)
 
293
        gdbm_close(u->gdbm_file);
 
294
 
 
295
    pa_xfree(u);
 
296
}