-struct rule {
- char* name;
- pa_cvolume volume;
-};
-
-struct userdata {
- pa_hashmap *hashmap;
- pa_subscription *subscription;
- int modified;
- char *table_file;
-};
-
-static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
- char *p;
- long k;
- unsigned i;
-
- assert(s);
- assert(v);
-
- if (!isdigit(*s))
- return NULL;
-
- k = strtol(s, &p, 0);
- if (k <= 0 || k > PA_CHANNELS_MAX)
- return NULL;
-
- v->channels = (unsigned) k;
-
- for (i = 0; i < v->channels; i++) {
- p += strspn(p, WHITESPACE);
-
- if (!isdigit(*p))
- return NULL;
-
- k = strtol(p, &p, 0);
-
- if (k < PA_VOLUME_MUTED)
- return NULL;
-
- v->values[i] = (pa_volume_t) k;
- }
-
- if (*p != 0)
- return NULL;
-
- return v;
-}
-
-static int load_rules(struct userdata *u) {
- FILE *f;
- int n = 0;
- int ret = -1;
- char buf_name[256], buf_volume[256];
- char *ln = buf_name;
-
- f = u->table_file ?
- fopen(u->table_file, "r") :
- pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r");
-
- if (!f) {
- if (errno == ENOENT) {
- pa_log_info(__FILE__": starting with empty ruleset.");
- ret = 0;
- } else
- pa_log(__FILE__": failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
-
- goto finish;
- }
-
- pa_lock_fd(fileno(f), 1);
-
- while (!feof(f)) {
- struct rule *rule;
- pa_cvolume v;
-
- if (!fgets(ln, sizeof(buf_name), f))
- break;
-
- n++;
-
- pa_strip_nl(ln);
-
- if (ln[0] == '#' || !*ln )
- continue;
-
- if (ln == buf_name) {
- ln = buf_volume;
- continue;
- }
-
- assert(ln == buf_volume);
-
- if (!parse_volume(buf_volume, &v)) {
- pa_log(__FILE__": parse failure in %s:%u, stopping parsing", u->table_file, n);
- goto finish;
- }
-
- ln = buf_name;
-
- if (pa_hashmap_get(u->hashmap, buf_name)) {
- pa_log(__FILE__": double entry in %s:%u, ignoring", u->table_file, n);
- goto finish;
- }
-
- rule = pa_xnew(struct rule, 1);
- rule->name = pa_xstrdup(buf_name);
- rule->volume = v;
-
- pa_hashmap_put(u->hashmap, rule->name, rule);
- }
-
- if (ln == buf_volume) {
- pa_log(__FILE__": invalid number of lines in %s.", u->table_file);
- goto finish;
- }
-
- ret = 0;
-
-finish:
- if (f) {
- pa_lock_fd(fileno(f), 0);
- fclose(f);
- }
-
- return ret;
-}
-
-static int save_rules(struct userdata *u) {
- FILE *f;
- int ret = -1;
- void *state = NULL;
- struct rule *rule;
-
- f = u->table_file ?
- fopen(u->table_file, "w") :
- pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w");
-
- if (!f) {
- pa_log(__FILE__": failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
- goto finish;
- }
-
- pa_lock_fd(fileno(f), 1);
-
- while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) {
- unsigned i;
-
- fprintf(f, "%s\n%u", rule->name, rule->volume.channels);
-
- for (i = 0; i < rule->volume.channels; i++)
- fprintf(f, " %u", rule->volume.values[i]);
-
- fprintf(f, "\n");
- }
-
- ret = 0;
-
-finish:
- if (f) {
- pa_lock_fd(fileno(f), 0);
- fclose(f);
- }
-
- return ret;
-}
-
-static char* client_name(pa_client *c) {
- char *t, *e;
-
- if (!c->name || !c->driver)
- return NULL;
-
- t = pa_sprintf_malloc("%s$%s", c->driver, c->name);
- t[strcspn(t, "\n\r#")] = 0;
-
- if (!*t)
- return NULL;
-
- if ((e = strrchr(t, '('))) {
- char *k = e + 1 + strspn(e + 1, "0123456789-");
-
- /* Dirty trick: truncate all trailing parens with numbers in
- * between, since they are usually used to identify multiple
- * sessions of the same application, which is something we
- * explicitly don't want. Besides other stuff this makes xmms
- * with esound work properly for us. */
-
- if (*k == ')' && *(k+1) == 0)
- *e = 0;
- }
-
- return t;
-}
-
-static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
- struct userdata *u = userdata;
- pa_sink_input *si;
- struct rule *r;
- char *name;
-
- assert(c);
- assert(u);
-
- if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
- t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
- return;
-
- if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
- return;
-
- if (!si->client || !(name = client_name(si->client)))
- return;
-
- if ((r = pa_hashmap_get(u->hashmap, name))) {
- pa_xfree(name);
-
- if (((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) && si->sample_spec.channels == r->volume.channels) {
- pa_log_info(__FILE__": Restoring volume for <%s>", r->name);
- pa_sink_input_set_volume(si, &r->volume);
- } else if (!pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
- pa_log_info(__FILE__": Saving volume for <%s>", r->name);
- r->volume = *pa_sink_input_get_volume(si);
- u->modified = 1;
- }
-
- } else {
- pa_log_info(__FILE__": Creating new entry for <%s>", name);
-
- r = pa_xnew(struct rule, 1);
- r->name = name;
- r->volume = *pa_sink_input_get_volume(si);
- pa_hashmap_put(u->hashmap, r->name, r);
-
- u->modified = 1;
- }
-}
-
-int pa__init(pa_core *c, pa_module*m) {