diff options
author | Lennart Poettering <lennart@poettering.net> | 2008-05-26 22:00:19 +0000 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2008-05-26 22:00:19 +0000 |
commit | d7fd6a45e50475cddf0b8bad8baab01b33cf3c1f (patch) | |
tree | 4f210adb96478280df083b6d4802053f93b59192 /src/sound-theme-spec.c | |
parent | 48178a5e2813546b61706d1f97fab761934a97f0 (diff) |
move sources to src/ subdir
git-svn-id: file:///home/lennart/svn/public/libcanberra/trunk@12 01b60673-d06a-42c0-afdd-89cb8e0f78ac
Diffstat (limited to 'src/sound-theme-spec.c')
-rw-r--r-- | src/sound-theme-spec.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/src/sound-theme-spec.c b/src/sound-theme-spec.c new file mode 100644 index 0000000..fd0f95b --- /dev/null +++ b/src/sound-theme-spec.c @@ -0,0 +1,632 @@ +/* $Id$ */ + +/*** + This file is part of libcanberra. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + <http://www.gnu.org/licenses/>. +***/ + +#include "sound-theme-spec.h" +#include "llist.h" + +#define DEFAULT_THEME "freedesktop" +#define FALLBACK_THEME "freedesktop" +#define DEFAULT_OUTPUT_PROFILE "stereo" +#define N_THEME_DIR_MAX 8 + +typedef struct ca_data_dir ca_data_dir; + +struct ca_data_dir { + CA_LLIST_FIELDS(ca_data_dir); + + char *name; + char *output_profile; +}; + +struct ca_theme_data { + char *name; + + CA_LLIST_HEAD(ca_data_dir, data_dirs); + ca_theme_dir *last_dir; + + unsigned n_theme_dir; + ca_bool_t loaded_fallback_theme; +}; + +static int get_data_home(char **e) { + const char *env, *subdir; + char *r; + ca_return_val_if_fail(e, CA_ERROR_INVALID); + + if ((env = getenv("XDG_DATA_HOME")) && *env == '/') + subdir = ""; + else if ((env = getenv("HOME")) && *env == '/') + subdir = "/.local/share"; + else { + *e = NULL; + return CA_SUCCESS; + } + + if (!(r = ca_new(char, strlen(env) + strlen(subdir) + 1))) + return CA_ERROR_OOM; + + sprintf(r, "%s%s", env, subdir); + *e = r; + + return CA_SUCCESS; +} + +static ca_bool_t data_dir_matches(ca_data_dir *d, const char*output_profile) { + ca_assert(d); + ca_assert(profile); + + /* We might want to add more elaborate matching here eventually */ + + return streq(d->profile, output_profile); +} + +static ca_data_dir* find_data_dir(ca_theme_data *t, const char *name) { + ca_data_dir *d; + + ca_assert(t); + ca_assert(name); + + for (d = t->data_dirs; d; d = d->next) + if (streq(d->name, name)) + return d; + + return NULL; +} + +static int add_data_dir(ca_theme_data *t, const char *name) { + ca_data_dir *d; + + ca_return_val_if_fail(t, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + + if (find_data_dir(t, name)) + return; + + if (!(d = ca_new0(ca_data_dir, 1))) + return CA_ERROR_OOM; + + if (!(d->name = ca_strdup(name))) { + ca_free(d); + return CA_ERROR_OOM; + } + + CA_LLIST_INSERT_AFTER(ca_data_dir, t->data_dirs, t->last_dir, d); + t->last_dir = d; + + return CA_SUCCESS; +} + +static int load_theme_path(ca_theme_data *t, const char *prefix, const char *name) { + char *fn, *inherits = NULL; + FILE *f; + ca_bool_t in_sound_theme_section = FALSE; + ca_data_dir *current_data_dir = NULL; + + ca_return_val_if_fail(t, CA_ERROR_INVALID); + ca_return_val_if_fail(prefix, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + + if (!(fn = ca_new(char, strlen(prefix) + sizeof("/sounds/")-1 + strlen(name) + sizeof("index.theme")))) + return CA_ERROR_OOM; + + sprintf(fn, "%s/sounds/%s/index.theme", prefix, name); + f = fopen(fn, "r"); + ca_free(fn); + + if (!f) { + if (errno == ENOENT) + return CA_ERROR_NOTFOUND; + + return CA_ERROR_SYSTEM; + } + + for (;;) { + char ln[1024]; + + if (!(fgets(ln, f))) { + + if (feof(f)) + break; + + ca_assert(ferror(f)); + ret = CA_ERROR_SYSTEM; + goto fail; + } + + ln[strcspn(ln, "\n\r#")] = 0; + + if (!ln[0]) + continue; + + if (streq(ln, "[Sound Theme]")) { + in_sound_theme_section = TRUE; + current_data_dir = NULL; + continue; + } + + if (ln[0] == '[' && ln[strlen(ln-1)] == ']') { + char *d; + + if (!(d = ca_strndup(ln+1, strlen(ln)-2))) { + ret = CA_ERROR_OOM; + goto fail; + } + + current_data_dir = find_data_dir(e, d); + ca_free(d); + + in_sound_theme_section = FALSE; + continue; + } + + ca_assert(!in_sound_theme_section || !current_data_dir); + ca_assert(!current_data_dir || !in_sound_theme_section); + + if (in_sound_theme_section) { + + if (!strncmp(ln, "Inherits", 8)) { + + if (inherits) { + ret = CA_ERROR_CORRUPT; + goto fail; + } + + if (!(inherits = ca_strdup(ln + 8))) { + ret = CA_ERROR_OOM; + goto fail; + } + + continue; + } + + if (!strncmp(ln, "Directories", 11)) { + char *d; + + d = ln+11; + for (;;) { + size_t k = strcspn(d, ", "); + + if (k > 0) { + char *p; + + if (!(p = ca_strndup(d, k))) { + ret = CA_ERROR_OOM; + goto fail; + } + + ret = add_data_dir(t, p); + ca_free(p); + + if (ret != CA_SUCCESS) + goto fail; + } + + if (d[k] == 0) + break; + + = k+1; + } + + continue; + } + } + + if (current_data_dir) { + + if (!strncmp(ln, "OutputProfile", 13)) { + + if (current_data_dir->output_profile && !streq(current_data_dir->output_profile, ln+13)) { + ret = CA_ERROR_CORRUPT; + goto fail; + } + + if (!(current_data_dir->output_profile = ca_strdup(ln+13))) { + ret = CA_ERROR_OOM; + goto fail; + } + + continue; + } + } + } + + t->n_theme_dir ++; + + if (inherits) { + i = inherits; + for (;;) { + size_t k = strcspn(i, ", "); + + if (k > 0) { + char *p; + + if (!(p = ca_strndup(i, k))) { + ret = CA_ERROR_OOM; + goto fail; + } + + ret = load_theme_dir(t, p); + ca_free(p); + + if (ret != CA_SUCCESS) + goto fail; + } + + if (i[k] == 0) + break; + + i = k+1; + } + } + + ret = CA_SUCCESS; + +fail: + + ca_free(inherits); + ca_free(directories); + fclose(f); + + return ret; +} + +static int load_theme_dir(ca_theme_data *t, const char *name) { + ca_return_val_if_fail(t, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(t->n_theme_dir < N_THEME_DIR_MAX, CA_ERROR_CORRUPT); + + if ((ret = get_data_home(&e)) < 0) + return ret; + + if (streq(name, FALLBACK_THEME)) + t->loaded_fallback_theme = TRUE; + + if (e) { + ret = load_theme_path(t, e, name); + ca_free(e); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + if (!(e = getenv("XDG_DATA_DIRS")) || *e = 0) + e = "/usr/local/share:/usr/share"; + + for (;;) { + size_t k; + + k = strcspn(e, ":"); + + if (e[0] == '/' && k > 0) { + char *p; + + if (!(p = ca_strndup(e, k))) + return CA_ERROR_OOM; + + ret = load_theme_path(t, p, name); + ca_free(p); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + if (e[k] == 0) + break; + + e += k+1; + } + + return CA_ERROR_NOTFOUND; +} + +static int load_theme_data(ca_theme_data **_t, const char *name) { + ca_theme_data *t; + int ret; + + ca_return_val_if_fail(_t, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + + if (*_t) + if (streq((*_t)->name, name)) + return CA_SUCCESS; + + if (!(t = pa_xnew0(ca_theme_data, 1))) + return CA_ERROR_OOM; + + if (!(t->name = ca_strdup(name))) { + ret = CA_ERROR_OOM; + goto fail; + } + + if ((ret = load_theme_dir(t, name)) < 0) + goto fail; + + if (!t->loaded_fallback_theme) + if ((ret = load_theme_dir(t, FALLBACK_THEME)) < 0) + goto fail; + + if (*_t) + ca_theme_data_free(*_t); + + *_t = t; + + return CA_SUCCESS; + +fail: + + if (t) + ca_theme_data_free(t); + + return ret; +} + +static int find_sound_for_suffix(ca_sound_file **f, ca_theme_data *t, const char *name, const char *path, const char *suffix, const char *locale, const char *subdir) { + const char *fn; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(path, CA_ERROR_INVALID); + ca_return_val_if_fail(path[0] == '/', CA_ERROR_INVALID); + ca_return_val_if_fail(suffix, CA_ERROR_INVALID); + + if (!(fn = ca_sprintf_malloc("%s/%s%s%s%s%s%s", + path, + t ? "/" : "", + t ? t->name : "", + subdir ? "/" : "" + subdir ? subdir : "", + locale ? "/" : "", + locale ? locale : "", + name, suffix))) + return CA_ERROR_OOM; + + ret = ca_sound_file_open(f, fn); + ca_free(fn); + + return ret; +} + +static int find_sound_in_path(ca_sound_file **f, ca_theme_data *t, const char *name, const char *path, const char *locale, const char *subdir) { + int ret; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(path, CA_ERROR_INVALID); + ca_return_val_if_fail(path[0] == '/', CA_ERROR_INVALID); + + if (!(p = ca_new(char, strlen(path) + sizeof("/sounds")))) + return CA_ERROR_OOM; + + sprintf(p, "%s/sounds", path); + + if ((ret = find_sound_for_suffix(f, t, name, p, ".ogg", locale, subdir)) == CA_ERROR_NOTFOUND) + ret = find_sound_for_suffix(f, t, name, p, ".wav", locale, subdir); + + ca_free(p); + + return ret; +} + +static int find_sound_in_subdir(ca_sound_file **f, ca_theme_data *t, const char *name, const char *locale, const char *subdir) { + int ret; + char *e = NULL; + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + + if ((ret = get_data_home(&e)) < 0) + return ret; + + if (e) { + ret = find_sound_in_path(f, t, name, e, locale, subdir); + ca_free(e); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + if (!(e = getenv("XDG_DATA_DIRS")) || *e = 0) + e = "/usr/local/share:/usr/share"; + + for (;;) { + size_t k; + + k = strcspn(e, ":"); + + if (e[0] == '/' && k > 0) { + char *p; + + if (!(p = ca_strndup(e, k))) + return CA_ERROR_OOM; + + ret = find_sound_in_path(f, t, name, p, locale, subdir); + ca_free(p); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + if (e[k] == 0) + break; + + e += k+1; + } + + return CA_ERROR_NOTFOUND; +} + +static int find_sound_for_profile(ca_sound_file **f, ca_theme_data *t, const char *name, const char *locale, const char *profile) { + ca_data_dir *d; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + + if (!t) + return find_sound_in_subdir(f, NULL, name, locale, NULL); + + for (d = t->data_dirs; d; d = d->next) + if (data_dir_matches(d, profile)) { + int ret; + + if ((ret = find_sound_in_subdir(f, t, name, locale, d->name)) != CA_ERROR_NOTFOUND) + return ret; + } + + return CA_ERROR_NOTFOUND; +} + +static int find_sound_in_locale(ca_sound_file **f, ca_theme_data *t, const char *name, const char *locale, const char *profile) { + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(profile, CA_ERROR_INVALID); + + /* First, try the profile def itself */ + if ((ret = find_sound_for_profile(f, t, name, locale, profile)) != CA_ERROR_NOTFOUND) + return ret; + + /* Then, fall back to stereo */ + if (!streq(profile, DEFAULT_OUTPUT_PROFILE)) + if ((ret = find_sound_for_profile(f, t, name, locale, DEFAULT_PROFILE)) != CA_ERROR_NOTFOUND) + return ret; + + /* And fall back to no profile */ + return find_sound_for_profile(f, t, name, locale, NULL); +} + +static int find_sound_for_locale(ca_sound_file **f, ca_theme_data *theme, const char *name, const char *locale, const char *profile) { + const char *e; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(locale, CA_ERROR_INVALID); + ca_return_val_if_fail(profile, CA_ERROR_INVALID); + + /* First, try the locale def itself */ + if ((ret = find_sound_in_locale(f, theme, name, locale, profile)) != CA_ERROR_NOTFOUND) + return ret; + + /* Then, try to truncate at the @ */ + if ((e = strchr(locale, '@'))) { + char *t; + + if (!(t = ca_strndup(t, e - locale))) + return CA_ERROR_OOM; + + ret = find_sound_in_locale(f, theme, name, t, profile); + ca_free(t); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + /* Followed by truncating at the _ */ + if ((e = strchr(locale, '_'))) { + char *t; + + if (!(t = ca_strndup(t, e - locale))) + return CA_ERROR_OOM; + + ret = find_sound_in_locale(f, theme, name, t, profile); + ca_free(t); + + if (ret != CA_ERROR_NOTFOUND) + return ret; + } + + /* Then, try "C" as fallback locale */ + if (strcmp(locale, "C")) + if ((ret = find_sound_in_locale(f, theme, name, "C", profile)) != CA_ERROR_NOTFOUND) + return ret; + + /* Try without locale */ + return find_sound_in_locale(f, theme, name, NULL, profile); +} + +static int find_sound_for_theme(ca_sound_file **f, ca_theme_data **t, const char *theme, const char *name, const char *locale, const char *profile) { + int ret; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(t, CA_ERROR_INVALID); + ca_return_val_if_fail(theme, CA_ERROR_INVALID); + ca_return_val_if_fail(name, CA_ERROR_INVALID); + ca_return_val_if_fail(locale, CA_ERROR_INVALID); + ca_return_val_if_fail(profile, CA_ERROR_INVALID); + + /* First, try in the theme itself */ + if ((ret = load_theme_data(t, theme)) == CA_SUCCESS) + if ((ret = find_sound_in_theme(f, t, name, locale, profile)) != CA_ERROR_NOTFOUND) + return ret; + + /* Then, fall back to "unthemed" files */ + return find_sound_in_theme(f, NULL, name, locale, profile); +} + +int ca_lookup_sound(ca_sound_file **f, ca_theme_data **t, ca_proplist *p) { + int ret; + const char *name, *fname; + + ca_return_val_if_fail(f, CA_ERROR_INVALID); + ca_return_val_if_fail(t, CA_ERROR_INVALID); + ca_return_val_if_fail(p, CA_ERROR_INVALID); + + ca_mutex_lock(p->mutex); + + if ((name = ca_proplist_gets(p, CA_PROP_EVENT_ID))) { + const char *theme, *locale, *profile; + + if (!(theme = ca_proplist_gets(p, CA_PROP_CANBERRA_XDG_THEME_NAME))) + theme = DEFAULT_THEME; + + if (!(locale = ca_proplist_gets(p, CA_PROP_APPLICATION_LANGUAGE))) + if (!(locale = setlocale(LC_MESSAGES, NULL))) + locale = "C"; + + if (!(profile = ca_proplist_gets(p, CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE))) + profile = DEFAULT_OUTPUT_PROFILE; + + ret = find_sound_for_theme(f, t, theme, name, locale, profile); + + } else if ((fname = ca_proplist_gets(p, CA_PROP_MEDIA_FILENAME))) + ret = ca_sound_file_open(f, fname); + else + ret = CA_ERROR_INVALID; + + ca_mutex_unlock(p->mutex); + + return ret; +} + +void ca_theme_data_free(ca_theme_data *t) { + ca_assert(t); + + while (t->data_dirs) { + ca_data_dir *d = t->data_dirs; + + CA_LLIST_REMOVE(ca_data_dir, t->data_dirs, d); + + ca_free(d->name); + ca_free(d->output_profile); + ca_free(d); + } + + ca_free(t->name); + ca_free(t); +} |