summaryrefslogtreecommitdiffstats
path: root/src/sound-theme-spec.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2008-05-26 22:00:19 +0000
committerLennart Poettering <lennart@poettering.net>2008-05-26 22:00:19 +0000
commitd7fd6a45e50475cddf0b8bad8baab01b33cf3c1f (patch)
tree4f210adb96478280df083b6d4802053f93b59192 /src/sound-theme-spec.c
parent48178a5e2813546b61706d1f97fab761934a97f0 (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.c632
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);
+}