summaryrefslogtreecommitdiffstats
path: root/src
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
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')
-rw-r--r--src/canberra.c1
-rw-r--r--src/canberra.h210
-rw-r--r--src/common.c380
-rw-r--r--src/common.h50
-rw-r--r--src/driver.h38
-rw-r--r--src/llist.h108
-rw-r--r--src/macro.h239
-rw-r--r--src/malloc.h41
-rw-r--r--src/mutex-posix.c80
-rw-r--r--src/mutex.h37
-rw-r--r--src/proplist.c317
-rw-r--r--src/proplist.h53
-rw-r--r--src/pulse.c903
-rw-r--r--src/read-sound-file.c175
-rw-r--r--src/read-sound-file.h48
-rw-r--r--src/read-vorbis.c148
-rw-r--r--src/read-vorbis.h38
-rw-r--r--src/read-wav.c247
-rw-r--r--src/read-wav.h42
-rw-r--r--src/sound-theme-spec.c632
-rw-r--r--src/sound-theme-spec.h34
-rw-r--r--src/test-gtk.c18
-rw-r--r--src/test.c79
23 files changed, 3918 insertions, 0 deletions
diff --git a/src/canberra.c b/src/canberra.c
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/canberra.c
@@ -0,0 +1 @@
+
diff --git a/src/canberra.h b/src/canberra.h
new file mode 100644
index 0000000..2d48cd8
--- /dev/null
+++ b/src/canberra.h
@@ -0,0 +1,210 @@
+#ifndef foocanberrahfoo
+#define foocanberrahfoo
+
+/* $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 <sys/types.h>
+#include <sys/param.h>
+#include <inttypes.h>
+
+/*
+
+ Requirements & General observations:
+
+ - Property set extensible. To be kept in sync with PulseAudio and libsydney.
+ - Property keys need to be valid UTF-8, text values, too.
+ - Will warn if application.name or application.id not set.
+ - Will fail if event.id not set
+ - Fully thread safe, not async-signal safe
+ - Error codes are returned immediately, as negative integers
+ - If the control.cache property is set it will control whether the
+ specific sample will be cached in the server:
+
+ * permanent: install the sample permanently in the server (for usage in gnome-session)
+ * volatile: install the sample temporarily in the server (will be expelled from cache on cache pressure or after timeout)
+ * never: never cache the sample in the server, always stream
+
+ control.cache will default to "volatile" for ca_context_cache() and "never" for ca_context_play().
+ control.cache is only a hint, the server may ignore this value
+ - application.process.* will be filled in automatically but may be overwritten by the client.
+ They thus should not be used for authentication purposes.
+ - The property list attached to the client object in the sound
+ server will be those specified via ca_context_prop_xx().
+ - The property list attached to cached samples in the sound server
+ will be those specified via ca_context_prop_xx() at sample upload time,
+ combined with those specified directly at the _cache() function call
+ (the latter potentially overwriting the former).
+ - The property list attached to sample streams in the sound server
+ will be those attached to the cached sample (only if the event
+ sound is cached, of course) combined (i.e. potentially
+ overwritten by) those set via ca_context_prop_xx() at play time,
+ combined (i.e. potentially overwritten by) those specified
+ directly at the _play() function call.
+ - It is recommended to set application.* once before calling
+ _open(), and media.* event.* at both cache and play time.
+
+*/
+
+#ifdef __GNUC__
+#define CA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b)))
+#else
+/** If we're in GNU C, use some magic for detecting invalid format strings */
+#define CA_GCC_PRINTF_ATTR(a,b)
+#endif
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define CA_GCC_SENTINEL __attribute__ ((sentinel))
+#else
+/** Macro for usage of GCC's sentinel compilation warnings */
+#define CA_GCC_SENTINEL
+#endif
+
+/** Properties for the media that is being played, about the event
+ * that caused the media to play, the window on which behalf this
+ * media is being played, the application that this window belongs to
+ * and finally properties for libcanberra specific usage. */
+#define CA_PROP_MEDIA_NAME "media.name"
+#define CA_PROP_MEDIA_TITLE "media.title"
+#define CA_PROP_MEDIA_ARTIST "media.artist"
+#define CA_PROP_MEDIA_LANGUAGE "media.language"
+#define CA_PROP_MEDIA_FILENAME "media.filename"
+#define CA_PROP_MEDIA_ICON "media.icon"
+#define CA_PROP_MEDIA_ICON_NAME "media.icon_name"
+#define CA_PROP_MEDIA_ROLE "media.role"
+#define CA_PROP_EVENT_ID "event.id"
+#define CA_PROP_EVENT_MOUSE_X "event.mouse.x"
+#define CA_PROP_EVENT_MOUSE_Y "event.mouse.y"
+#define CA_PROP_EVENT_MOUSE_BUTTON "event.mouse.button"
+#define CA_PROP_WINDOW_NAME "window.name"
+#define CA_PROP_WINDOW_ID "window.id"
+#define CA_PROP_WINDOW_ICON "window.icon"
+#define CA_PROP_WINDOW_ICON_NAME "window.icon_name"
+#define CA_PROP_WINDOW_X11_DISPLAY "window.x11.display"
+#define CA_PROP_WINDOW_X11_XID "window.x11.xid"
+#define CA_PROP_APPLICATION_NAME "application.name"
+#define CA_PROP_APPLICATION_ID "application.id"
+#define CA_PROP_APPLICATION_VERSION "application.version"
+#define CA_PROP_APPLICATION_ICON "application.icon"
+#define CA_PROP_APPLICATION_ICON_NAME "application.icon_name"
+#define CA_PROP_APPLICATION_LANGUAGE "application.language"
+#define CA_PROP_APPLICATION_PROCESS_ID "application.process.id"
+#define CA_PROP_APPLICATION_PROCESS_BINARY "application.process.binary"
+#define CA_PROP_APPLICATION_PROCESS_USER "application.process.user"
+#define CA_PROP_APPLICATION_PROCESS_HOST "application.process.host"
+#define CA_PROP_CANBERRA_CACHE_CONTROL "canberra.cache-control" /* permanent, volatile, never */
+#define CA_PROP_CANBERRA_VOLUME "canberra.volume" /* decibel */
+#define CA_PROP_CANBERRA_XDG_THEME_NAME "canberra.xdg-theme.name" /* XDG theme name */
+#define CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE "canberra.xdg-theme.output-profile" /* XDG theme profile */
+
+/* Context object */
+typedef struct ca_context ca_context;
+
+/** Playback completion event callback. This callback will be called
+ * from a background thread. */
+typedef void ca_finish_callback_t(ca_context *c, uint32_t id, int error_code, void *userdata);
+
+/** Error codes */
+enum {
+ CA_SUCCESS = 0,
+ CA_ERROR_NOTSUPPORTED = -1,
+ CA_ERROR_INVALID = -2,
+ CA_ERROR_STATE = -3,
+ CA_ERROR_OOM = -4,
+ CA_ERROR_NODRIVER = -5,
+ CA_ERROR_SYSTEM = -6,
+ CA_ERROR_CORRUPT = -7,
+ CA_ERROR_TOOBIG = -8,
+ CA_ERROR_NOTFOUND = -9,
+ CA_ERROR_DESTROYED = -10,
+ CA_ERROR_CANCELED = -11,
+ CA_ERROR_NOTAVAILABLE = -12,
+ CA_ERROR_ACCESS = -13,
+ CA_ERROR_IO = -14,
+ _CA_ERROR_MAX = -15
+};
+
+typedef struct ca_proplist ca_proplist;
+
+int ca_proplist_create(ca_proplist **c);
+int ca_proplist_destroy(ca_proplist *c);
+int ca_proplist_sets(ca_proplist *p, const char *key, const char *value);
+int ca_proplist_setf(ca_proplist *p, const char *key, const char *format, ...) CA_GCC_PRINTF_ATTR(3,4);
+int ca_proplist_set(ca_proplist *p, const char *key, const void *data, size_t nbytes);
+
+/** Create an (unconnected) context object */
+int ca_context_create(ca_context **c);
+
+int ca_context_set_driver(ca_context **c, const char *driver);
+
+int ca_context_change_device(ca_context **c, const char *device);
+
+/** Connect the context. This call is implicitly called if necessary. It
+ * is recommended to initialize the application.* properties before
+ * issuing this call */
+int ca_context_open(ca_context *c);
+
+/** Destroy a (connected or unconnected) cntext object. */
+int ca_context_destroy(ca_context *c);
+
+/** Write one or more string properties to the context
+ * object. Requires final NULL sentinel. Properties set like this will
+ * be attached to both the client object of the sound server and to
+ * all event sounds played or cached. */
+int ca_context_change_props(ca_context *c, ...) CA_GCC_SENTINEL;
+
+/** Write an arbitrary data property to the context object. */
+int ca_context_change_props_full(ca_context *c, ca_proplist *p);
+
+/** Play one event sound. id can be any numeric value which later can
+ * be used to cancel an event sound that is currently being
+ * played. You may use the same id twice or more times if you want to
+ * cancel multiple event sounds with a single ca_context_cancel() call
+ * at once. It is recommended to pass 0 for the id if the event sound
+ * shall never be canceled. If the requested sound is not cached in
+ * the server yet this call might result in the sample being uploaded
+ * temporarily or permanently. This function will only start playback
+ * in the background. It will not wait until playback completed. */
+int ca_context_play(ca_context *c, uint32_t id, ...) CA_GCC_SENTINEL;
+
+/** Play one event sound, and call the specified callback function
+ when completed. The callback will be called from a background
+ thread. Other arguments identical to ca_context_play(). */
+int ca_context_play_full(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_callback_t cb, void *userdata);
+
+/** Upload the specified sample into the server and attach the
+ * specified properties to it. This function will only return after
+ * the sample upload was finished. */
+int ca_context_cache(ca_context *c, ...) CA_GCC_SENTINEL;
+
+/** Upload the specified sample into the server and attach the
+ * specified properties to it */
+int ca_context_cache_full(ca_context *c, ca_proplist *p);
+
+/** Cancel one or more event sounds that have been started via
+ * ca_context_play(). */
+int ca_context_cancel(ca_context *c, uint32_t id);
+
+/** Return a human readable error string */
+const char *ca_strerror(int code);
+
+#endif
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..bf11450
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,380 @@
+/* $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 <stdarg.h>
+
+#include "canberra.h"
+#include "common.h"
+#include "malloc.h"
+#include "driver.h"
+#include "proplist.h"
+
+int ca_context_create(ca_context **_c) {
+ ca_context *c;
+ int ret;
+
+ ca_return_val_if_fail(_c, CA_ERROR_INVALID);
+
+ if (!(c = ca_new0(ca_context, 1)))
+ return CA_ERROR_OOM;
+
+ if (!(c->mutex = ca_mutex_new())) {
+ ca_free(c);
+ return CA_ERROR_OOM;
+ }
+
+ if ((ret = ca_proplist_create(&c->props)) < 0) {
+ ca_mutex_free(c->mutex);
+ ca_free(c);
+ return ret;
+ }
+
+ *_c = c;
+ return CA_SUCCESS;
+}
+
+int ca_context_destroy(ca_context *c) {
+ int ret = CA_SUCCESS;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ /* There's no locking necessary here, because the application is
+ * broken anyway if it destructs this object in one thread and
+ * still is calling a method of it in another. */
+
+ if (c->opened)
+ ret = driver_destroy(c);
+
+ if (c->props)
+ ca_assert_se(ca_proplist_destroy(c->props) == CA_SUCCESS);
+
+ ca_mutex_free(c->mutex);
+ ca_free(c->driver);
+ ca_free(c->device);
+ ca_free(c);
+
+ return ret;
+}
+
+int ca_context_set_driver(ca_context *c, char *driver) {
+ char *n;
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_mutex_lock(c->mutex);
+ ca_return_val_if_fail_unlock(!c->opened, CA_ERROR_STATE, c->mutex);
+
+ if (!(n = ca_strdup(driver))) {
+ ret = CA_ERROR_OOM;
+ goto fail;
+ }
+
+ ca_free(c->driver);
+ c->driver = n;
+
+ ret = CA_SUCCESS;
+
+fail:
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+int ca_context_change_device(ca_context *c, char *device) {
+ char *n;
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_mutex_lock(c->mutex);
+
+ if (!(n = ca_strdup(device))) {
+ ret = CA_ERROR_OOM;
+ goto fail;
+ }
+
+ ret = c->opened ? driver_change_device(c, n) : CA_SUCCESS;
+
+ if (ret == CA_SUCCESS) {
+ ca_free(c->device);
+ c->device = n;
+ } else
+ ca_free(n);
+
+fail:
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+static int context_open_unlocked(ca_context *c) {
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ if (c->opened)
+ return CA_SUCCESS;
+
+ if ((ret = driver_open(c)) == CA_SUCCESS)
+ c->opened = TRUE;
+
+ return ret;
+}
+
+int ca_context_open(ca_context *c) {
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_mutex_lock(c->mutex);
+ ca_return_val_if_fail_unlock(!c->opened, CA_ERROR_STATE, c->mutex);
+
+ ret = context_open_unlocked(c);
+
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+static int ca_proplist_from_ap(ca_proplist **_p, va_list ap) {
+ int ret;
+ ca_proplist *p;
+
+ ca_assert(_p);
+
+ if ((ret = ca_proplist_create(&p)) < 0)
+ return ret;
+
+ for (;;) {
+ const char *key, *value;
+ int ret;
+
+ if (!(key = va_arg(ap, const char*)))
+ break;
+
+ if (!(value = va_arg(ap, const char*))) {
+ ret = CA_ERROR_INVALID;
+ goto fail;
+ }
+
+ if ((ret = ca_proplist_sets(p, key, value)) < 0)
+ goto fail;
+ }
+
+ *_p = p;
+
+ return CA_SUCCESS;
+
+fail:
+ ca_assert_se(ca_proplist_destroy(p) == CA_SUCCESS);
+
+ return ret;
+}
+
+int ca_context_change_props(ca_context *c, ...) {
+ va_list ap;
+ int ret;
+ ca_proplist *p = NULL;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ va_start(ap, c);
+ ret = ca_proplist_from_ap(&p, ap);
+ va_end(ap);
+
+ if (ret < 0)
+ return ret;
+
+ ret = ca_context_change_props_full(c, p);
+
+ ca_assert_se(ca_proplist_destroy(p) == 0);
+
+ return ret;
+}
+
+int ca_context_change_props_full(ca_context *c, ca_proplist *p) {
+ int ret;
+ ca_proplist *merged;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+
+ ca_mutex_lock(c->mutex);
+
+ if ((ret = ca_proplist_merge(&merged, c->props, p)) < 0)
+ goto finish;
+
+ ret = c->opened ? driver_change_props(c, p, merged) : CA_SUCCESS;
+
+ if (ret == CA_SUCCESS) {
+ ca_assert_se(ca_proplist_destroy(c->props) == CA_SUCCESS);
+ c->props = merged;
+ } else
+ ca_assert_se(ca_proplist_destroy(merged) == CA_SUCCESS);
+
+finish:
+
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+int ca_context_play(ca_context *c, uint32_t id, ...) {
+ int ret;
+ va_list ap;
+ ca_proplist *p = NULL;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ va_start(ap, id);
+ ret = ca_proplist_from_ap(&p, ap);
+ va_end(ap);
+
+ if (ret < 0)
+ return ret;
+
+ ret = ca_context_play_full(c, id, p, NULL, NULL);
+
+ ca_assert_se(ca_proplist_destroy(p) == 0);
+
+ return ret;
+}
+
+int ca_context_play_full(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_callback_t cb, void *userdata) {
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(!userdata || cb, CA_ERROR_INVALID);
+
+ ca_mutex_lock(c->mutex);
+
+ ca_return_val_if_fail_unlock(ca_proplist_contains(p, CA_PROP_EVENT_ID) ||
+ ca_proplist_contains(c->props, CA_PROP_EVENT_ID), CA_ERROR_INVALID, c->mutex);
+
+ if ((ret = context_open_unlocked(c)) < 0)
+ goto finish;
+
+ ca_assert(c->opened);
+
+ ret = driver_play(c, id, p, cb, userdata);
+
+finish:
+
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+int ca_context_cancel(ca_context *c, uint32_t id) {
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_mutex_lock(c->mutex);
+ ca_return_val_if_fail_unlock(c->opened, CA_ERROR_STATE, c->mutex);
+
+ ret = driver_cancel(c, id);
+
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+int ca_context_cache(ca_context *c, ...) {
+ int ret;
+ va_list ap;
+ ca_proplist *p = NULL;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ va_start(ap, c);
+ ret = ca_proplist_from_ap(&p, ap);
+ va_end(ap);
+
+ if (ret < 0)
+ return ret;
+
+ ret = ca_context_cache_full(c, p);
+
+ ca_assert_se(ca_proplist_destroy(p) == 0);
+
+ return ret;
+}
+
+int ca_context_cache_full(ca_context *c, ca_proplist *p) {
+ int ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+
+ ca_mutex_lock(c->mutex);
+
+ ca_return_val_if_fail_unlock(ca_proplist_contains(p, CA_PROP_EVENT_ID) ||
+ ca_proplist_contains(c->props, CA_PROP_EVENT_ID), CA_ERROR_INVALID, c->mutex);
+
+ if ((ret = context_open_unlocked(c)) < 0)
+ goto finish;
+
+ ca_assert(c->opened);
+
+ ret = driver_cache(c, p);
+
+finish:
+
+ ca_mutex_unlock(c->mutex);
+
+ return ret;
+}
+
+/** Return a human readable error */
+const char *ca_strerror(int code) {
+
+ const char * const error_table[-_CA_ERROR_MAX] = {
+ [-CA_SUCCESS] = "Success",
+ [-CA_ERROR_NOT_SUPPORTED] = "Operation not supported",
+ [-CA_ERROR_INVALID] = "Invalid argument",
+ [-CA_ERROR_STATE] = "Invalid state",
+ [-CA_ERROR_OOM] = "Out of memory",
+ [-CA_ERROR_NO_DRIVER] = "No such driver",
+ [-CA_ERROR_SYSTEM] = "System error"
+ };
+
+ ca_return_val_if_fail(code <= 0, NULL);
+ ca_return_val_if_fail(code > _CA_ERROR_MAX, NULL);
+
+ return error_table[-code];
+}
+
+/* Not exported */
+int ca_parse_cache_control(ca_cache_control_t *control, const char *c) {
+ ca_return_val_if_fail(control, CA_ERROR_INVALID);
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ if (streq(control, "never"))
+ *control = CA_CACHE_CONTROL_NEVER;
+ else if (streq(control, "permanent"))
+ *control = CA_CACHE_CONTROL_PERMANENT;
+ else if (streq(control, "volatile"))
+ *control = CA_CACHE_CONTROL_VOLATILE;
+ else
+ return CA_ERROR_INVALID;
+
+ return CA_SUCCESS;
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..13139b3
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,50 @@
+#ifndef foocacommonh
+#define foocacommonh
+
+/* $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 "canberra.h"
+#include "macro.h"
+#include "mutex.h"
+
+struct ca_context {
+ ca_bool_t opened;
+ ca_mutex *mutex;
+
+ ca_proplist *props;
+
+ char *driver;
+ char *device;
+
+ void *private;
+};
+
+typedef enum ca_cache_control {
+ CA_CACHE_CONTROL_NEVER,
+ CA_CACHE_CONTROL_PERMANENT,
+ CA_CACHE_CONTROL_VOLATILE
+} ca_cache_control_t;
+
+int ca_parse_cache_control(ca_cache_control_t *control, const char *c);
+
+#endif
diff --git a/src/driver.h b/src/driver.h
new file mode 100644
index 0000000..732a5d4
--- /dev/null
+++ b/src/driver.h
@@ -0,0 +1,38 @@
+#ifndef foocanberradriverhfoo
+#define foocanberradriverhfoo
+
+/* $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 "canberra.h"
+
+int driver_open(ca_context *c);
+int driver_destroy(ca_context *c);
+
+int driver_change_device(ca_context *c, char *device);
+int driver_change_props(ca_context *c, ca_proplist *changed, ca_proplist *merged);
+
+int driver_play(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_callback_t cb, void *userdata);
+int driver_cancel(ca_context *c, uint32_t id);
+int driver_cache(ca_context *c, ca_proplist *p);
+
+#endif
diff --git a/src/llist.h b/src/llist.h
new file mode 100644
index 0000000..e9122ff
--- /dev/null
+++ b/src/llist.h
@@ -0,0 +1,108 @@
+#ifndef foocallistfoo
+#define foocallistfoo
+
+/* $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 "macro.h"
+
+/* Some macros for maintaining doubly linked lists */
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define CA_LLIST_HEAD(t,name) \
+ t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define CA_LLIST_FIELDS(t) \
+ t *next, *prev
+
+/* Initialize the list's head */
+#define CA_LLIST_HEAD_INIT(t,item) \
+ do { \
+ (item) = (t*) NULL; } \
+ while(0)
+
+/* Initialize a list item */
+#define CA_LLIST_INIT(t,item) \
+ do { \
+ t *_item = (item); \
+ ca_assert(_item); \
+ _item->prev = _item->next = NULL; \
+ } while(0)
+
+/* Prepend an item to the list */
+#define CA_LLIST_PREPEND(t,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ ca_assert(_item); \
+ if ((_item->next = *_head)) \
+ _item->next->prev = _item; \
+ _item->prev = NULL; \
+ *_head = _item; \
+ } while (0)
+
+/* Remove an item from the list */
+#define CA_LLIST_REMOVE(t,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ ca_assert(_item); \
+ if (_item->next) \
+ _item->next->prev = _item->prev; \
+ if (_item->prev) \
+ _item->prev->next = _item->next; \
+ else { \
+ ca_assert(*_head == _item); \
+ *_head = _item->next; \
+ } \
+ _item->next = _item->prev = NULL; \
+ } while(0)
+
+/* Find the head of the list */
+#define CA_LLIST_FIND_HEAD(t,item,head) \
+ do { \
+ t **_head = (head), *_item = (item); \
+ *_head = _item; \
+ ca_assert(_head); \
+ while ((*_head)->prev) \
+ *_head = (*_head)->prev; \
+ } while (0)
+
+/* Insert an item after another one (a = where, b = what) */
+#define CA_LLIST_INSERT_AFTER(t,head,a,b) \
+ do { \
+ t **_head = &(head), *_a = (a), *_b = (b); \
+ ca_assert(_b); \
+ if (!_a) { \
+ if ((_b->next = *_head)) \
+ _b->next->prev = _b; \
+ _b->prev = NULL; \
+ *_head = _b; \
+ } else { \
+ if ((_b->next = _a->next)) \
+ _b->next->prev = _b; \
+ _b->prev = _a; \
+ _a->next = _b; \
+ } \
+ } while (0)
+
+#endif
diff --git a/src/macro.h b/src/macro.h
new file mode 100644
index 0000000..2f37bc1
--- /dev/null
+++ b/src/macro.h
@@ -0,0 +1,239 @@
+#ifndef foocanberramacrohfoo
+#define foocanberramacrohfoo
+
+/* $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 <stdio.h>
+#include <stdlib.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#ifdef __GNUC__
+#define CA_LIKELY(x) (__builtin_expect(!!(x),1))
+#define CA_UNLIKELY(x) (__builtin_expect((x),0))
+#else
+#define CA_LIKELY(x) (x)
+#define CA_UNLIKELY(x) (x)
+#endif
+
+#ifdef __GNUC__
+#define CA_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#define CA_PRETTY_FUNCTION ""
+#endif
+
+#define ca_return_if_fail(expr) \
+ do { \
+ if (CA_UNLIKELY(!(expr))) { \
+ fprintf(stderr, "Assertion '%s' failed at %s:%u, function %s().", #expr , __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ return; \
+ } \
+ } while(FALSE)
+
+#define ca_return_val_if_fail(expr, val) \
+ do { \
+ if (CA_UNLIKELY(!(expr))) { \
+ fprintf(stderr, "Assertion '%s' failed at %s:%u, function %s().", #expr , __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ return (val); \
+ } \
+ } while(FALSE)
+
+#define ca_return_null_if_fail(expr) ca_return_val_if_fail(expr, NULL)
+
+#define ca_return_if_fail_unlock(expr, mutex) \
+ do { \
+ if (CA_UNLIKELY(!(expr))) { \
+ fprintf(stderr, "Assertion '%s' failed at %s:%u, function %s().", #expr , __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ ca_mutex_unlock(mutex); \
+ return; \
+ } \
+ } while(FALSE)
+
+#define ca_return_val_if_fail_unlock(expr, val, mutex) \
+ do { \
+ if (CA_UNLIKELY(!(expr))) { \
+ fprintf(stderr, "Assertion '%s' failed at %s:%u, function %s().", #expr , __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ ca_mutex_unlock(mutex); \
+ return (val); \
+ } \
+ } while(FALSE)
+
+#define ca_return_null_if_fail_unlock(expr, mutex) ca_return_val_if_fail_unlock(expr, NULL, mutex)
+
+/* An assert which guarantees side effects of x, i.e. is never
+ * optimized away */
+#define ca_assert_se(expr) \
+ do { \
+ if (CA_UNLIKELY(!(expr))) { \
+ fprintf(stderr, "Assertion '%s' failed at %s:%u, function %s(). Aborting.", #expr , __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ abort(); \
+ } \
+ } while (FALSE)
+
+/* An assert that may be optimized away by defining NDEBUG */
+#ifdef NDEBUG
+#define ca_assert(expr) do {} while (FALSE)
+#else
+#define ca_assert(expr) ca_assert_se(expr)
+#endif
+
+#define ca_assert_not_reached() \
+ do { \
+ fprintf(stderr, "Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, CA_PRETTY_FUNCTION); \
+ abort(); \
+ } while (FALSE)
+
+#define CA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#ifdef __GNUC__
+#define CA_MAX(a,b) \
+ __extension__ ({ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+#else
+#define CA_MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifdef __GNUC__
+#define CA_MIN(a,b) \
+ __extension__ ({ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+#else
+#define CA_MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifdef __GNUC__
+#define CA_CLAMP(x, low, high) \
+ __extension__ ({ typeof(x) _x = (x); \
+ typeof(low) _low = (low); \
+ typeof(high) _high = (high); \
+ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
+ })
+#else
+#define CA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+#endif
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+#define CA_PTR_TO_UINT(p) ((unsigned int) (unsigned long) (p))
+#define CA_UINT_TO_PTR(u) ((void*) (unsigned long) (u))
+
+#define CA_PTR_TO_UINT32(p) ((uint32_t) CA_PTR_TO_UINT(p))
+#define CA_UINT32_TO_PTR(u) CA_UINT_TO_PTR((uint32_t) u)
+
+#define CA_PTR_TO_INT(p) ((int) CA_PTR_TO_UINT(p))
+#define CA_INT_TO_PTR(u) CA_UINT_TO_PTR((int) u)
+
+#define CA_PTR_TO_INT32(p) ((int32_t) CA_PTR_TO_UINT(p))
+#define CA_INT32_TO_PTR(u) CA_UINT_TO_PTR((int32_t) u)
+
+
+
+static inline size_t ca_align(size_t l) {
+ return (((l + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*));
+}
+
+#define CA_ALIGN(x) (ca_align(x))
+
+typedef void (*ca_free_cb_t)(void *);
+
+typedef int ca_bool_t;
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+#ifdef HAVE_BYTESWAP_H
+#define PA_INT16_SWAP(x) ((int16_t) bswap_16((uint16_t) x))
+#define PA_UINT16_SWAP(x) ((uint16_t) bswap_16((uint16_t) x))
+#define PA_INT32_SWAP(x) ((int32_t) bswap_32((uint32_t) x))
+#define PA_UINT32_SWAP(x) ((uint32_t) bswap_32((uint32_t) x))
+#else
+#define PA_INT16_SWAP(x) ( (int16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) )
+#define PA_UINT16_SWAP(x) ( (uint16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) )
+#define PA_INT32_SWAP(x) ( (int32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) )
+#define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) )
+#endif
+
+#ifdef WORDS_BIGENDIAN
+ #define PA_INT16_FROM_LE(x) PA_INT16_SWAP(x)
+ #define PA_INT16_FROM_BE(x) ((int16_t)(x))
+
+ #define PA_INT16_TO_LE(x) PA_INT16_SWAP(x)
+ #define PA_INT16_TO_BE(x) ((int16_t)(x))
+
+ #define PA_UINT16_FROM_LE(x) PA_UINT16_SWAP(x)
+ #define PA_UINT16_FROM_BE(x) ((uint16_t)(x))
+
+ #define PA_UINT16_TO_LE(x) PA_UINT16_SWAP(x)
+ #define PA_UINT16_TO_BE(x) ((uint16_t)(x))
+
+ #define PA_INT32_FROM_LE(x) PA_INT32_SWAP(x)
+ #define PA_INT32_FROM_BE(x) ((int32_t)(x))
+
+ #define PA_INT32_TO_LE(x) PA_INT32_SWAP(x)
+ #define PA_INT32_TO_BE(x) ((int32_t)(x))
+
+ #define PA_UINT32_FROM_LE(x) PA_UINT32_SWAP(x)
+ #define PA_UINT32_FROM_BE(x) ((uint32_t)(x))
+
+ #define PA_UINT32_TO_LE(x) PA_UINT32_SWAP(x)
+ #define PA_UINT32_TO_BE(x) ((uint32_t)(x))
+#else
+ #define PA_INT16_FROM_LE(x) ((int16_t)(x))
+ #define PA_INT16_FROM_BE(x) PA_INT16_SWAP(x)
+
+ #define PA_INT16_TO_LE(x) ((int16_t)(x))
+ #define PA_INT16_TO_BE(x) PA_INT16_SWAP(x)
+
+ #define PA_UINT16_FROM_LE(x) ((uint16_t)(x))
+ #define PA_UINT16_FROM_BE(x) PA_UINT16_SWAP(x)
+
+ #define PA_UINT16_TO_LE(x) ((uint16_t)(x))
+ #define PA_UINT16_TO_BE(x) PA_UINT16_SWAP(x)
+
+ #define PA_INT32_FROM_LE(x) ((int32_t)(x))
+ #define PA_INT32_FROM_BE(x) PA_INT32_SWAP(x)
+
+ #define PA_INT32_TO_LE(x) ((int32_t)(x))
+ #define PA_INT32_TO_BE(x) PA_INT32_SWAP(x)
+
+ #define PA_UINT32_FROM_LE(x) ((uint32_t)(x))
+ #define PA_UINT32_FROM_BE(x) PA_UINT32_SWAP(x)
+
+ #define PA_UINT32_TO_LE(x) ((uint32_t)(x))
+ #define PA_UINT32_TO_BE(x) PA_UINT32_SWAP(x)
+#endif
+
+#endif
diff --git a/src/malloc.h b/src/malloc.h
new file mode 100644
index 0000000..b720a12
--- /dev/null
+++ b/src/malloc.h
@@ -0,0 +1,41 @@
+#ifndef foosydneymallochfoo
+#define foocanberramallochfoo
+
+/* $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 <stdlib.h>
+#include <string.h>
+
+#define ca_malloc malloc
+#define ca_free free
+#define ca_malloc0(size) calloc(1, (size))
+#define ca_strdup strdup
+#define ca_strndup strndup
+
+void* ca_memdup(const void* p, size_t size);
+
+#define ca_new(t, n) ((t*) ca_malloc(sizeof(t)*(n)))
+#define ca_new0(t, n) ((t*) ca_malloc0(sizeof(t)*(n)))
+#define ca_newdup(t, p, n) ((t*) ca_memdup(p, sizeof(t)*(n)))
+
+#endif
diff --git a/src/mutex-posix.c b/src/mutex-posix.c
new file mode 100644
index 0000000..f8e0f54
--- /dev/null
+++ b/src/mutex-posix.c
@@ -0,0 +1,80 @@
+/* $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/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <errno.h>
+
+#include "mutex.h"
+#include "malloc.h"
+
+struct ca_mutex {
+ pthread_mutex_t mutex;
+};
+
+ca_mutex* ca_mutex_new(void) {
+ ca_mutex *m;
+
+ if (!(m = ca_new(ca_mutex, 1)))
+ return NULL;
+
+ if (pthread_mutex_init(&m->mutex, NULL) < 0) {
+ ca_free(m);
+ return NULL;
+ }
+
+ return m;
+}
+
+void ca_mutex_free(ca_mutex *m) {
+ ca_assert(m);
+
+ ca_assert_se(pthread_mutex_destroy(&m->mutex) == 0);
+ ca_free(m);
+}
+
+void ca_mutex_lock(ca_mutex *m) {
+ ca_assert(m);
+
+ ca_assert_se(pthread_mutex_lock(&m->mutex) == 0);
+}
+
+ca_bool_t ca_mutex_try_lock(ca_mutex *m) {
+ int r;
+ ca_assert(m);
+
+ if ((r = pthread_mutex_trylock(&m->mutex)) != 0) {
+ ca_assert(r == EBUSY);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void ca_mutex_unlock(ca_mutex *m) {
+ ca_assert(m);
+
+ ca_assert_se(pthread_mutex_unlock(&m->mutex) == 0);
+}
diff --git a/src/mutex.h b/src/mutex.h
new file mode 100644
index 0000000..31b75b3
--- /dev/null
+++ b/src/mutex.h
@@ -0,0 +1,37 @@
+#ifndef foocamutexhfoo
+#define foocamutexhfoo
+
+/* $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 "macro.h"
+
+typedef struct ca_mutex ca_mutex;
+
+ca_mutex* ca_mutex_new(void);
+void ca_mutex_free(ca_mutex *m);
+
+void ca_mutex_lock(ca_mutex *m);
+ca_bool_t ca_mutex_try_lock(ca_mutex *m);
+void ca_mutex_unlock(ca_mutex *m);
+
+#endif
diff --git a/src/proplist.c b/src/proplist.c
new file mode 100644
index 0000000..6c10595
--- /dev/null
+++ b/src/proplist.c
@@ -0,0 +1,317 @@
+/* $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 <stdarg.h>
+
+#include "canberra.h"
+#include "proplist.h"
+#include "macro.h"
+#include "malloc.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+static unsigned calc_hash(const char *c) {
+ unsigned hash = 0;
+
+ for (; *c; c++)
+ hash = 31 * hash + *c;
+
+ return hash;
+}
+
+int ca_proplist_create(ca_proplist **_p) {
+ ca_proplist *p;
+ ca_return_val_if_fail(_p, CA_ERROR_INVALID);
+
+ if (!(p = ca_new0(ca_proplist, 1)))
+ return CA_ERROR_OOM;
+
+ if (!(p->mutex = ca_mutex_new())) {
+ ca_free(p);
+ return CA_ERROR_OOM;
+ }
+
+ *_p = p;
+
+ return CA_SUCCESS;
+}
+
+static int _unset(ca_proplist *p, const char *key) {
+ ca_prop *prop, *nprop;
+ unsigned i;
+
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(key, CA_ERROR_INVALID);
+
+ i = calc_hash(key) % N_HASHTABLE;
+
+ nprop = NULL;
+ for (prop = p->prop_hashtable[i]; prop; nprop = prop, prop = prop->next_in_slot)
+ if (strcmp(prop->key, key) == 0)
+ break;
+
+ if (prop) {
+ if (nprop)
+ nprop->next_in_slot = prop->next_in_slot;
+ else
+ p->prop_hashtable[i] = prop->next_in_slot;
+
+ if (prop->prev_item)
+ prop->prev_item->next_item = prop->next_item;
+ else
+ p->first_item = prop->next_item;
+
+ if (prop->next_item)
+ prop->next_item->prev_item = prop->prev_item;
+
+ ca_free(prop);
+ }
+
+ return CA_SUCCESS;
+}
+
+int ca_proplist_sets(ca_proplist *p, const char *key, const char *value) {
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(key, CA_ERROR_INVALID);
+ ca_return_val_if_fail(!value, CA_ERROR_INVALID);
+
+ return ca_proplist_set(p, key, value, sizeof(value)+1);
+}
+
+int ca_proplist_setf(ca_proplist *p, const char *key, const char *format, ...) {
+ int ret;
+ char *k;
+ ca_prop *prop;
+ int size = 100;
+ unsigned h;
+
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(key, CA_ERROR_INVALID);
+ ca_return_val_if_fail(format, CA_ERROR_INVALID);
+
+ if (!(k = ca_strdup(key)))
+ return CA_ERROR_OOM;
+
+ for (;;) {
+ va_list ap;
+ int r;
+
+ if (!(prop = ca_malloc(CA_ALIGN(sizeof(ca_prop)) + size))) {
+ ca_free(k);
+ return CA_ERROR_OOM;
+ }
+
+
+ va_start(ap, format);
+ r = vsnprintf(CA_PROP_DATA(prop), size, format, ap);
+ va_end(ap);
+
+ ((char*) CA_PROP_DATA(prop))[size-1] = 0;
+
+ if (r > -1 && r < size) {
+ prop->nbytes = r+1;
+ break;
+ }
+
+ if (r > -1) /* glibc 2.1 */
+ size = r+1;
+ else /* glibc 2.0 */
+ size *= 2;
+
+ ca_free(prop);
+ }
+
+ prop->key = k;
+
+ ca_mutex_lock(p->mutex);
+
+ if ((ret = _unset(p, key)) < 0) {
+ ca_free(prop);
+ ca_free(k);
+ goto finish;
+ }
+
+ h = calc_hash(key) % N_HASHTABLE;
+
+ prop->next_in_slot = p->prop_hashtable[h];
+ p->prop_hashtable[h] = prop;
+
+ prop->prev_item = NULL;
+ prop->next_item = p->first_item;
+ p->first_item = prop;
+
+finish:
+
+ ca_mutex_unlock(p->mutex);
+
+ return ret;
+}
+
+int ca_proplist_set(ca_proplist *p, const char *key, const void *data, size_t nbytes) {
+ int ret;
+ char *k;
+ ca_prop *prop;
+ unsigned h;
+
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(key, CA_ERROR_INVALID);
+ ca_return_val_if_fail(!nbytes || data, CA_ERROR_INVALID);
+
+ if (!(k = ca_strdup(key)))
+ return CA_ERROR_OOM;
+
+ if (!(prop = ca_malloc(CA_ALIGN(sizeof(ca_prop)) + nbytes))) {
+ ca_free(k);
+ return CA_ERROR_OOM;
+ }
+
+ prop->key = k;
+ prop->nbytes = nbytes;
+ memcpy(CA_PROP_DATA(prop), data, nbytes);
+
+ ca_mutex_lock(p->mutex);
+
+ if ((ret = _unset(p, key)) < 0) {
+ ca_free(prop);
+ ca_free(k);
+ goto finish;
+ }
+
+ h = calc_hash(key) % N_HASHTABLE;
+
+ prop->next_in_slot = p->prop_hashtable[h];
+ p->prop_hashtable[h] = prop;
+
+ prop->prev_item = NULL;
+ prop->next_item = p->first_item;
+ p->first_item = prop;
+
+finish:
+
+ ca_mutex_unlock(p->mutex);
+
+ return ret;
+}
+
+/* Not exported, not self-locking */
+ca_prop* ca_proplist_get_unlocked(ca_proplist *p, const char *key) {
+ ca_prop *prop;
+ unsigned i;
+
+ ca_return_val_if_fail(p, NULL);
+ ca_return_val_if_fail(key, NULL);
+
+ i = calc_hash(key) % N_HASHTABLE;
+
+ for (prop = p->prop_hashtable[i]; prop; prop = prop->next_in_slot)
+ if (strcmp(prop->key, key) == 0)
+ return prop;
+
+ return NULL;
+}
+
+/* Not exported, not self-locking */
+const char* ca_proplist_gets_unlocked(ca_proplist *p, const char *key) {
+ ca_prop *prop;
+
+ ca_return_val_if_fail(p, NULL);
+ ca_return_val_if_fail(key, NULL);
+
+ if (!(prop = ca_proplist_get_unlocked(p, key)))
+ return NULL;
+
+ if (!memchr(CA_PROP_DATA(prop), 0, prop->nbytes))
+ return NULL;
+
+ return CA_PROP_DATA(prop);
+}
+
+int ca_proplist_destroy(ca_proplist *p) {
+ ca_prop *prop, *nprop;
+
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+
+ for (prop = p->first_item; prop; prop = nprop) {
+ nprop = prop->next_item;
+ ca_free(prop);
+ }
+
+ ca_mutex_free(p->mutex);
+
+ ca_free(p);
+
+ return CA_SUCCESS;
+}
+
+static int merge_into(ca_proplist *a, ca_proplist *b) {
+ int ret = CA_SUCCESS;
+ ca_prop *prop;
+
+ ca_return_val_if_fail(a, CA_ERROR_INVALID);
+ ca_return_val_if_fail(b, CA_ERROR_INVALID);
+
+ ca_mutex_lock(b->mutex);
+
+ for (prop = b->first_item; prop; prop = prop->next_item)
+ if ((ret = ca_proplist_set(a, prop->key, CA_PROP_DATA(prop), prop->nbytes)) < 0)
+ break;
+
+ ca_mutex_unlock(b->mutex);
+
+ return ret;
+}
+
+int ca_proplist_merge(ca_proplist **_a, ca_proplist *b, ca_proplist *c) {
+ ca_proplist *a;
+ int ret;
+
+ ca_return_val_if_fail(_a, CA_ERROR_INVALID);
+ ca_return_val_if_fail(b, CA_ERROR_INVALID);
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ if ((ret = ca_proplist_create(&a)) < 0)
+ return ret;
+
+ if ((ret = merge_into(a, b)) < 0 ||
+ (ret = merge_into(a, c)) < 0) {
+ ca_proplist_destroy(a);
+ return ret;
+ }
+
+ *_a = a;
+ return CA_SUCCESS;
+}
+
+ca_bool_t ca_proplist_contains(ca_proplist *p, const char *key) {
+ ca_bool_t b;
+
+ ca_return_val_if_fail(p, CA_ERROR_INVALID);
+ ca_return_val_if_fail(key, CA_ERROR_INVALID);
+
+ ca_mutex_lock(p->mutex);
+ b = !!ca_proplist_get_unlocked(p, key);
+ ca_mutex_unlock(p->mutex);
+
+ return b;
+}
diff --git a/src/proplist.h b/src/proplist.h
new file mode 100644
index 0000000..4cb511e
--- /dev/null
+++ b/src/proplist.h
@@ -0,0 +1,53 @@
+#ifndef foocaproplisthfoo
+#define foocaproplisthfoo
+
+/* $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 "canberra.h"
+#include "mutex.h"
+
+#define N_HASHTABLE 31
+
+typedef struct ca_prop {
+ char *key;
+ size_t nbytes;
+ struct ca_prop *next_in_slot, *next_item, *prev_item;
+} ca_prop;
+
+#define CA_PROP_DATA(p) ((void*) ((char*) (p) + CA_ALIGN(sizeof(ca_prop))))
+
+struct ca_proplist {
+ ca_mutex *mutex;
+
+ ca_prop *prop_hashtable[N_HASHTABLE];
+ ca_prop *first_item;
+};
+
+int ca_proplist_merge(ca_proplist **_a, ca_proplist *b, ca_proplist *c);
+ca_bool_t ca_proplist_contains(ca_proplist *p, const char *key);
+
+/* Both of the following two functions are not locked! Need manual locking! */
+ca_prop* ca_context_get_unlocked(ca_proplist *c, const char *key);
+const char* ca_context_gets_unlocked(ca_proplist *c, const char *key);
+
+#endif
diff --git a/src/pulse.c b/src/pulse.c
new file mode 100644
index 0000000..c69668a
--- /dev/null
+++ b/src/pulse.c
@@ -0,0 +1,903 @@
+/* $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/>.
+***/
+
+/* The locking order needs to be strictly followed! First take the
+ * mainloop mutex, only then take outstanding_mutex if you need both!
+ * Not the other way round, beacause we might then enter a
+ * deadlock! */
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/scache.h>
+
+#include "canberra.h"
+#include "common."
+#include "driver.h"
+
+enum outstanding_type {
+ OUTSTANDING_SAMPLE,
+ OUTSTANDING_STREAM,
+ OUTSTANDING_UPLOAD
+};
+
+struct outstanding {
+ PA_LLIST_FIELDS(struct outstanding);
+ enum outstanding_type type;
+ ca_context *context;
+ uint32_t id;
+ uint32_t sink_input;
+ ca_stream *stream;
+ ca_finish_callback_t callback;
+ void *userdata;
+ ca_sound_file *file;
+ int error;
+ ca_bool_t clean_up;
+};
+
+struct private {
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+ ca_theme_data *theme;
+ ca_bool_t subscribed;
+
+ ca_mutex *outstanding_mutex;
+ PA_LLIST_HEAD(struct outstanding, outstanding);
+};
+
+#define PRIVATE(c) ((struct private *) ((c)->private)
+
+static void outstanding_free(struct outstanding *o) {
+ ca_assert(o);
+
+ if (o->file)
+ ca_sound_file_free(o->file);
+
+ if (o->stream) {
+ pa_stream_disconnect(o->stream);
+ pa_stream_unref(o->stream);
+ }
+
+ ca_free(o);
+}
+
+static int convert_proplist(pa_proplist **_l, pa_proplist *c) {
+ pa_proplist *l;
+ ca_prop *i;
+
+ ca_return_val_if_fail(_l, CA_ERROR_INVALID);
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+
+ if (!(l = pa_proplist_new()))
+ return CA_ERROR_OOM;
+
+ ca_mutex_lock(c->mutex);
+
+ for (i = c->first_item; i; i = i->next_item)
+ if (pa_proplist_put(l, i->key, CA_PROP_DATA(i), i->nbytes) < 0) {
+ ca_mutex_unlock(c->mutex);
+ pa_proplist_free(l);
+ return PA_ERROR_INVALID;
+ }
+
+ ca_mutex_unlock(c->mutex);
+
+ *_l = l;
+
+ return PA_SUCCESS;
+}
+
+static pa_proplist *strip_canberra_data(pa_proplist *l) {
+ const char *key;
+ void *state = NULL;
+ ca_assert(l);
+
+ while ((key = pa_proplist_iterate(l, &state)))
+ if (strncmp(key, "canberra.", 12) == 0)
+ pa_proplist_remove(l, key);
+
+ return l;
+}
+
+static int translate_error(int error) {
+ static const int table[PA_ERR_MAX] = {
+ [PA_OK] = CA_SUCCESS,
+ [PA_ERR_ACCESS] = CA_ERROR_ACCESS,
+ [PA_ERR_COMMAND] = CA_ERROR_IO,
+ [PA_ERR_INVALID] = CA_ERROR_INVALID,
+ [PA_ERR_EXIST] = CA_ERROR_IO,
+ [PA_ERR_NOENTITY] = CA_ERROR_NOTFOUND,
+ [PA_ERR_CONNECTIONREFUSED] = CA_ERROR_NOTAVAILABLE,
+ [PA_ERR_PROTOCOL] = CA_ERROR_IO,
+ [PA_ERR_TIMEOUT] = CA_ERROR_IO,
+ [PA_ERR_AUTHKEY] = CA_ERROR_ACCESS,
+ [PA_ERR_INTERNAL] = CA_ERROR_IO,
+ [PA_ERR_CONNECTIONTERMINATED] = CA_ERROR_IO,
+ [PA_ERR_KILLED] = CA_ERROR_DESTROYED,
+ [PA_ERR_INVALIDSERVER] = CA_ERROR_INVALID,
+ [PA_ERR_MODINITFAILED] = CA_ERROR_NODRIVER,
+ [PA_ERR_BADSTATE] = CA_ERROR_STATE,
+ [PA_ERR_NODATA] = CA_ERROR_IO,
+ [PA_ERR_VERSION] = CA_ERROR_NOTSUPPORTED,
+ [PA_ERR_TOOLARGE] = CA_ERROR_TOOBIG
+ };
+
+ ca_assert(error >= 0);
+
+ if (error >= PA_ERR_MAX)
+ return CA_ERROR_IO;
+
+ return table[error];
+}
+
+static void context_state_cb(pa_context *pc, void *userdata) {
+ ca_context *c = userdata;
+ pa_context_state_t state;
+
+ ca_assert(pc);
+ ca_assert(c);
+
+ state = pa_context_get_state(pc);
+
+ if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
+ struct outstanding *out;
+ int ret;
+
+ ret = translate_error(pa_context_errno(pc));
+
+ ca_mutex_lock(c->outstanding_mutex);
+
+ while ((out = c->outstanding)) {
+
+ PA_LLIST_REMOVE(struct outstanding, c->outstanding, out);
+ ca_mutex_unlock(c->outstanding_mutex);
+
+ if (out->callback)
+ out->callback(c, out->id, ret, out->userdata);
+ outstanding_free(c->outstanding);
+
+ ca_mutex_lock(c->outstanding_mutex);
+ }
+
+ ca_mutex_unlock(c->outstanding_mutex);
+ }
+
+ pa_threaded_mainloop_signal(c->mainloop, FALSE);
+}
+
+static void context_subscribe_cb(pa_context *pc, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct outstanding *out, *n;
+ PA_LLIST_HEAD(struct outstanding, l);
+ ca_context *c = userdata;
+
+ ca_assert(pc);
+ ca_assert(c);
+
+ if (t != PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE)
+ return;
+
+ PA_LLIST_HEAD_INIT(struct outstanding, l);
+
+ ca_mutex_lock(c->outstanding_mutex);
+
+ for (out = c->outstanding; out; out = n) {
+ n = out->next;
+
+ if (out->type != OUTSTANDING_SAMPLE || out->sink_input != idx)
+ continue;
+
+ PA_LLIST_REMOVE(struct outstanding, c->outstanding, out);
+ PA_LLIST_PREPEND(struct outstanding, l, out);
+ }
+
+ ca_mutex_unlock(c->outstanding_mutex);
+
+ while (l) {
+ out = l;
+
+ PA_LLIST_REMOVE(struct outstanding, l, out);
+
+ if (out->callback)
+ out->callback(c, out->id, CA_SUCCESS, out->userdata);
+
+ outstanding_free(out);
+ }
+}
+
+int driver_open(ca_context *c) {
+ pa_proplist *l;
+ struct private *p;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(!c->driver || streq(c->driver, "pulse"), PA_ERROR_NO_DRIVER);
+ ca_return_val_if_fail(!PRIVATE(c), PA_ERROR_STATE);
+
+ if (!(p = PRIVATE(c) = ca_new0(struct private, 1)))
+ return PA_ERROR_OOM;
+
+ if (!(p->mainloop = pa_threaded_mainloop_new())) {
+ driver_destroy(c);
+ return PA_ERROR_OOM;
+ }
+
+ if ((ret = convert_proplist(&l, c->proplist))) {
+ driver_destroy(c);
+ return ret;
+ }
+
+ if (!(p->context = pa_context_new_with_proplist(pa_threaded_mainloop_get_api(p->mainloop), l))) {
+ pa_proplist_free(l);
+ driver_destroy(c);
+ return PA_ERROR_OOM;
+ }
+
+ pa_proplist_free(l);
+
+ pa_context_set_state_callback(p->context, context_state_cb, c);
+ pa_context_set_subscribe_callback(p->context, context_subscribe_cb, c);
+
+ if (pa_context_connect(p->context, NULL, 0, NULL) < 0) {
+ int ret = translate_error(pa_context_errno(p->context));
+ driver_destroy(c);
+ return ret;
+ }
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ if (pa_threaded_mainloop_start(p->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(p->mainloop);
+ driver_destroy(c);
+ return PA_ERROR_INTERNAL;
+ }
+
+ pa_threaded_mainloop_wait(p->mainloop);
+
+ if (pa_context_get_state(p->context) != PA_CONTEXT_READY) {
+ int ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ driver_destroy(c);
+ return ret;
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return CA_SUCCESS;
+}
+
+int driver_destroy(ca_context *c) {
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ if (p->mainloop)
+ pa_threaded_mainloop_stop(p->mainloop);
+
+ if (p->context) {
+ pa_context_disconnect(p->context);
+ pa_context_unref(p->context);
+ }
+
+ if (p->mainloop)
+ pa_threaded_mainloop_free(p->mainloop);
+
+ if (p->theme)
+ ca_theme_data_free(p->theme);
+
+ while (p->outstanding) {
+ struct outstanding *out = p->outstanding;
+ PA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
+
+ if (out->callback)
+ out->callback(c, out->id, CA_ERROR_DESTROYED, out->userdata);
+
+ outstanding_free(out);
+ }
+
+ ca_free(p);
+}
+
+int driver_change_device(ca_context *c, char *device) {
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+
+ /* We're happy with any device change. We might however add code
+ * here eventually to move all currently played back event sounds
+ * to the new device. */
+
+ return CA_SUCCESS;
+}
+
+int driver_change_props(ca_context *c, ca_proplist *changed, ca_proplist *merged) {
+ struct private *p;
+ pa_operation *o;
+ pa_proplist *l;
+ int ret = CA_SUCCESS;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(changed, PA_ERROR_INVALID);
+ ca_return_val_if_fail(merged, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE);
+ ca_return_val_if_fail(p->context, PA_ERROR_STATE);
+
+ if ((ret = convert_proplist(&l, changed)))
+ return ret;
+
+ strip_canberra_data(l);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ /* We start these asynchronously and don't care about the return
+ * value */
+
+ if (!(o = pa_context_proplist_update(p->context, PA_UPDATE_REPLACE, l, NULL, NULL)))
+ ret = translate_error(pa_context_errno(p->context));
+ else
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ pa_proplist_free(l);
+
+ return ret;
+}
+
+static int subscribe(ca_context *c) {
+ struct private *p;
+ pa_operation *o;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+ p = PRIVATE(c);
+
+ ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE);
+ ca_return_val_if_fail(p->context, PA_ERROR_STATE);
+ ca_return_val_if_fail(!p->subscribed, PA_SUCCESS);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ /* We start these asynchronously and don't care about the return
+ * value */
+
+ if (!(o = pa_context_subscribe(p->context, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL)))
+ ret = translate_error(pa_context_errno(p->context));
+ else
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ p->subscribed = TRUE;
+
+ return ret;
+}
+
+static void play_sample_cb(pa_context *c, uint32_t idx, void *userdata) {
+ struct outstanding *out = userdata;
+
+ ca_assert(c);
+ ca_assert(out);
+
+ if (idx != PA_INVALID_INDEX) {
+ out->error = CA_SUCCESS;
+ out->sink_input = idx;
+ } else
+ out->error = translate_error(pa_context_errno(c));
+}
+
+static void stream_state_cb(pa_stream *s, void *userdata) {
+ struct private *p;
+ struct outstanding *out = userdata;
+
+ ca_assert(s);
+ ca_assert(out);
+
+ p = PRIVATE(out->context);
+
+ if (out->clean_up) {
+ pa_stream_state_t state;
+
+ state = pa_stream_get_state(s);
+
+ if (state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED) {
+ int err;
+
+ ca_mutex_lock(p->context->outstanding_mutex);
+ PA_LLIST_REMOVE(struct outstanding, c->outstanding, out);
+ ca_mutex_unlock(p->context->outstanding_mutex);
+
+ err = state == PA_STREAM_FAILED ? translate_error(pa_context_errno(pa_stream_get_context(s)))
+
+ if (out->callback)
+ out->callback(c, out->id, ret, out->userdata);
+
+ outstanding_free(c->outstanding);
+ }
+ }
+
+ pa_threaded_mainloop_signal(c->mainloop, TRUE);
+}
+
+static void stream_drain_cb(pa_stream *s, int success, void *userdata) {
+ struct private *p;
+ struct outstanding *out = userdata;
+
+ ca_assert(s);
+ ca_assert(out);
+
+ p = PRIVATE(out->context);
+
+ ca_assert(out->type = OUTSTANDING_STREAM);
+ ca_assert(out->clean_up);
+
+ ca_mutex_lock(p->context->outstanding_mutex);
+ PA_LLIST_REMOVE(struct outstanding, c->outstanding, out);
+ ca_mutex_unlock(p->context->outstanding_mutex);
+
+ if (out->callback) {
+ int err;
+
+ err = success ? CA_SUCCESS : translate_error(pa_context_errno(p->context));
+ out->callback(out->context, out->id, err, out->userdata);
+ }
+
+ outstanding_free(out);
+}
+
+static void stream_write_cb(pa_stream *s, size_t bytes, void *userdata) {
+ struct outstanding *out = userdata;
+ struct private *p;
+ void *data;
+ int ret;
+
+ ca_assert(s);
+ ca_assert(bytes > 0);
+ ca_assert(out);
+
+ p = PRIVATE(out->context);
+
+ if (!(data = ca_malloc(bytes))) {
+ ret = CA_ERROR_OOM
+ goto finish;
+ }
+
+ if ((ret = ca_sound_file_read_arbitrary(out->file, data, &bytes)) < 0)
+ goto finish;
+
+ if (bytes > 0) {
+
+ if ((ret = pa_stream_writesp, data, bytes)) < 0) {
+ ret = translate_error(ret);
+ goto finish;
+ }
+
+ } else {
+ /* We reached EOF */
+
+ if (out->type == OUTSTANDING_UPLOAD) {
+
+ if (pa_stream_finish_upload(s) < 0) {
+ ret = translate_error(pa_context_errno(p->context));
+ goto finish;
+ }
+
+ /* Let's just signal driver_cache() which has been waiting for us */
+ pa_threaded_mainloop_signal(c->mainloop, TRUE);
+
+ } else {
+ ca_assert(out->type = OUTSTANDING_STREAM);
+
+ if (!(o = pa_stream_drain(p->context, stream_drain_cb, out))) {
+ ret = translate_error(pa_context_errno(p->context));
+ goto fail;
+ }
+
+ pa_operation_unref(o);
+ }
+ }
+
+ ca_free(data);
+
+ return;
+
+finish:
+
+ ca_free(data);
+
+ if (out->clean_up) {
+ ca_mutex_lock(p->context->outstanding_mutex);
+ PA_LLIST_REMOVE(struct outstanding, c->outstanding, out);
+ ca_mutex_unlock(p->context->outstanding_mutex);
+
+ if (out->callback)
+ out->callback(out->context, out->id, ret, out->userdata);
+
+ outstanding_free(out);
+ } else {
+ pa_stream_disconnect(p);
+ pa_threaded_mainloop_signal(c->mainloop, TRUE);
+ out->error = ret;
+ }
+}
+
+static const pa_sample_format_t sample_type_table[] = {
+ [CA_SAMPLE_S16NE] = PA_SAMPLE_S16NE,
+ [CA_SAMPLE_S16RE] = PA_SAMPLE_S16RE,
+ [CA_SAMPLE_U8] = PA_SAMPLE_U8
+};
+
+int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_callback_t cb, void *userdata) {
+ struct private *p;
+ pa_proplist *l = NULL;
+ const char *n, *vol, *ct;
+ char *name = NULL;
+ int err = 0;
+ pa_volume_t v = PA_VOLUME_NORM;
+ pa_sample_spec ss;
+ ca_cache_control_t cache_control = CA_CACHE_CONTROL_NEVER;
+ struct outstanding *out = NULL;
+ int try = 3;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(proplist, PA_ERROR_INVALID);
+ ca_return_val_if_fail(!userdata || cb, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE);
+ ca_return_val_if_fail(p->context, PA_ERROR_STATE);
+
+ if (!(out = pa_xnew0(struct outstanding, 1))) {
+ ret = PA_ERROR_OOM;
+ goto finish;
+ }
+
+ out->type = OUTSTANDING_SAMPLE;
+ out->context = c;
+ out->sink_input = PA_INVALID_INDEX;
+ out->id = id;
+ out->callback = cb;
+ out->userdata = userdata;
+
+ if ((ret = convert_proplist(&l, proplist)))
+ goto finish;
+
+ if (!(n = pa_proplist_gets(l, CA_PROP_EVENT_ID))) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ if (!(name = ca_strdup(n))) {
+ ret = PA_ERROR_OOM;
+ goto finish;
+ }
+
+ if ((vol = pa_proplist_gets(l, CA_PROP_CANBERRA_VOLUME))) {
+ char *e = NULL;
+ double dvol;
+
+ errno = 0;
+ dvol = strtod(vol, &e);
+ if (errno != 0 || !e || *e) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ v = pa_sw_volume_from_dB(dvol);
+ }
+
+ if ((ct = pa_proplist_gets(l, CA_PROP_CANBERRA_CACHE_CONTROL)))
+ if ((ret = ca_parse_cache_control(&cache_control, ct)) < 0) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ strip_canberra_data(l);
+
+ if (cb)
+ if ((ret = subscribe(c)) < 0)
+ goto finish;
+
+ for (;;) {
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ /* Let's try to play the sample */
+ if (!(o = pa_context_play_sample_with_proplist(p->context, name, NULL, v, l, id, play_sample_cb, out))) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto finish;
+ }
+
+ while (pa_operation_get_state(o) != OPERATION_DONE)
+ pa_threaded_mainloop_wait(m);
+
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ /* Did we manage to play the sample or did some other error occur? */
+ if (out->error != CA_ERROR_NOT_FOUND)
+ goto finish;
+
+ /* Hmm, we need to play it directly */
+ if (cache_control == CA_CACHE_CONTROL_NEVER)
+ break;
+
+ /* Don't loop forever */
+ if (--try <= 0)
+ break;
+
+ /* Let's upload the sample and retry playing */
+ if ((ret = driver_cache(c, proplist)) < 0)
+ goto fail;
+ }
+
+ out->type = OUTSTANDING_STREAM;
+
+ /* Let's stream the sample directly */
+ if ((ret = ca_lookup_sound(&out->file, &p->theme, proplist)) < 0)
+ goto fail;
+
+ ss.channels = sample_type_table[ca_sound_file_get_sample_type(f)];
+ ss.channels = ca_sound_file_get_nchannels(f);
+ ss.rate = ca_sound_file_get_rate(f);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l))) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ pa_stream_set_userdata(p->stream, out);
+ pa_stream_set_state_callback(p->stream, stream_state_cb, out);
+ pa_stream_set_write_callback(p->stream, stream_request_cb, out);
+
+ if (pa_stream_connect_playback(s, NULL, NULL, 0, NULL, NULL) < 0) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ for (;;) {
+ pa_stream_state state = pa_stream_get_state(s);
+
+ /* Stream sucessfully created */
+ if (state == PA_STREAM_READY)
+ break;
+
+ /* Check for failure */
+ if (state == PA_STREAM_FAILED) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ if (state == PA_STREAM_TERMINATED) {
+ ret = out->error;
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ pa_threaded_mainloop_wait(p->mainloop);
+ }
+
+ if ((out->sink_input = pa_stream_get_index(s)) == PA_INVALID_INDEX) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ ret = CA_SUCCESS;
+
+finish:
+
+ /* We keep the outstanding struct around if we need clean up later to */
+ if (ret == CA_SUCCESS && (out->type == OUTSTANDING_STREAM || cb)) {
+ out->clean_up = TRUE;
+
+ pa_mutex_lock(p->outstanding_mutex);
+ PA_LLIST_PREPEND(struct outstanding, p->outstanding, out);
+ pa_mutex_unlock(p->outstanding_mutex);
+ } else
+ outstanding_free(out);
+
+ if (l)
+ pa_proplist_free(l);
+
+ ca_free(name);
+
+ return ret;
+}
+
+int driver_cancel(ca_context *c, uint32_t id) {
+ struct private *p;
+ pa_operation *o;
+ int ret = CA_SUCCESS;
+ struct outstanding *out, *n;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE);
+ ca_return_val_if_fail(p->context, PA_ERROR_STATE);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ ca_mutex_lock(p->outstanding_mutex);
+
+ /* We start these asynchronously and don't care about the return
+ * value */
+
+ for (out = p->outstanding; out; out = n) {
+ int ret2;
+ n = out->next;
+
+ if (out->type == OUTSTANDING_UPLOAD ||
+ out->id != id ||
+ out->sink_input == PA_INVALID_INDEX)
+ continue;
+
+ if (!(o = pa_context_kill_sink_input(p->context, out->sink_input, NULL, NULL)))
+ ret2 = translate_error(pa_context_errno(p->context));
+ else
+ pa_operation_unref(o);
+
+ /* We make sure here to kill all streams identified by the id
+ * here. However, we will return only the first error we
+ * encounter */
+
+ if (ret2 && ret == CA_SUCCESS)
+ ret = ret2;
+
+ if (out->callback)
+ out->callback(c, out->id, CA_ERROR_CANCELED, out->userdata);
+
+ CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
+ outstanding_free(out);
+ }
+
+ ca_mutex_unlock(p->outstanding_mutex);
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return ret;
+}
+
+int driver_cache(ca_context *c, ca_proplist *proplist) {
+ struct private *p;
+ pa_proplist *l = NULL;
+ const char *n, *ct;
+ char *name = NULL;
+ pa_sample_spec ss;
+ ca_cache_control_t cache_control = CA_CACHE_CONTROL_NEVER;
+ struct outstanding out;
+
+ ca_return_val_if_fail(c, PA_ERROR_INVALID);
+ ca_return_val_if_fail(proplist, PA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, PA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE);
+ ca_return_val_if_fail(p->context, PA_ERROR_STATE);
+
+ if (!(out = ca_new0(struct outstanding, 1))) {
+ ret = CA_ERROR_OOM;
+ goto finish;
+ }
+
+ out->type = OUTSTANDING_UPLOAD;
+ out->context = c;
+ out->sink_input = PA_INVALID_INDEX;
+
+ if ((ret = convert_proplist(&l, proplist)))
+ goto finish;
+
+ if (!(n = pa_proplist_gets(l, CA_PROP_EVENT_ID))) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ if (!(name = ca_strdup(n))) {
+ ret = PA_ERROR_OOM;
+ goto finish;
+ }
+
+ if ((ct = pa_proplist_gets(l, CA_PROP_CANBERRA_CACHE_CONTROL)))
+ if ((ret = ca_parse_cache_control(&cache_control, ct)) < 0) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ if (ct == CA_CACHE_CONTROL_NEVER) {
+ ret = PA_ERROR_INVALID;
+ goto finish;
+ }
+
+ strip_canberra_data(l);
+
+ /* Let's stream the sample directly */
+ if ((ret = ca_lookup_sound(&out->file, &p->theme, proplist)) < 0)
+ goto fail;
+
+ ss.channels = sample_type_table[ca_sound_file_get_sample_type(out->file)];
+ ss.channels = ca_sound_file_get_nchannels(out->file);
+ ss.rate = ca_sound_file_get_rate(out->file);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l))) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ pa_stream_set_userdata(out->stream, out);
+ pa_stream_set_state_callback(out->stream, stream_state_cb, out);
+ pa_stream_set_write_callback(out->stream, stream_request_cb, out);
+
+ if (pa_stream_connect_upload(s, ca_sound_file_get_size(out->file)) < 0) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ for (;;) {
+ pa_stream_state state = pa_stream_get_state(s);
+
+ /* Stream sucessfully created and uploaded */
+ if (state == PA_STREAM_TERMINATED)
+ break;
+
+ /* Check for failure */
+ if (state == PA_STREAM_FAILED) {
+ ret = translate_error(pa_context_errno(p->context));
+ pa_threaded_mainloop_unlock(p->mainloop);
+ goto fail;
+ }
+
+ pa_threaded_mainloop_wait(p->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ ret = CA_SUCCESS;
+
+finish:
+
+ outstanding_free(out);
+
+ if (l)
+ pa_proplist_free(l);
+
+ ca_free(name);
+
+ return ret;
+}
diff --git a/src/read-sound-file.c b/src/read-sound-file.c
new file mode 100644
index 0000000..8670445
--- /dev/null
+++ b/src/read-sound-file.c
@@ -0,0 +1,175 @@
+/* $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 "read-sound-file.h"
+
+struct ca_sound_file {
+ ca_wav *wav;
+ ca_vorbis *vorbis;
+ char *filename;
+
+ unsigned nchannels;
+ unsigned rate;
+ ca_sample_type_t type;
+};
+
+int ca_sound_file_open(ca_sound_file *_f, const char *fn) {
+ FILE *file;
+ ca_sound_file *f;
+ int ret;
+
+ ca_return_val_if_fail(_f, PA_ERROR_INVALID);
+ ca_return_val_if_fail(fn, PA_ERROR_INVALID);
+
+ if (!(f = ca_new0(ca_sound_file, 1)))
+ return CA_ERROR_OOM;
+
+ if (!(f->filename = ca_strdup(fn))) {
+ ret = CA_ERROR_OOM;
+ goto fail;
+ }
+
+ if (!(file = fopen(fn, "r"))) {
+ ret = errno == ENOENT ? CA_ERROR_NOTFOUND : CA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if ((ret = ca_wav_open(&f->wav, file)) == CA_SUCCESS) {
+ f->nchannels = ca_wav_get_nchannels(f->wav);
+ f->rate = ca_wav_get_rate(f->wav);
+ f->type = ca_wav_get_sample_type(f->wav);
+ *f = f;
+ return CA_SUCCESS;
+ }
+
+ if (ret == CA_ERROR_CORRUPT) {
+
+ if (fseek(file, 0, SEEK_SET) < 0) {
+ ret = CA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if ((ret = ca_vorbis_open(&f->vorbis, file)) == CA_SUCCESS) {
+ f->nchannels = ca_vorbis_get_nchannels(f->vorbis);
+ f->rate = ca_vorbis_get_rate(f->vorbis);
+ f->type = CA_SAMPLE_S16NE;
+ *f = f;
+ return CA_SUCCESS;
+ }
+ }
+
+fail:
+
+ ca_free(f->filename);
+ ca_free(f);
+
+ return ret;
+}
+
+void ca_sound_file_close(ca_sound_file *f) {
+ ca_assert(f);
+
+ if (f->wav)
+ ca_wav_free(f->wav);
+ if (f->vorbis)
+ ca_vorbis_free(f->vorbis);
+ ca_free(f);
+}
+
+unsigned ca_sound_file_get_nchannels(ca_sound_file *f) {
+ ca_assert(f);
+ return f->nchannels;
+}
+
+unsigned ca_sound_file_get_rate(ca_sound_file *f) {
+ ca_assert(f);
+ return f->nchannels;
+}
+
+ca_sample_type_t ca_sound_file_get_sample_type(ca_sound_file *f) {
+ ca_assert(f);
+ return f->type;
+}
+
+int ca_sound_file_read_int16(ca_sound_file *f, int16_t *d, unsigned *n) {
+ ca_return_val_if_fail(f, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+ ca_return_val_if_fail(f->wav || f->vorbis, CA_ERROR_STATE);
+ ca_return_val_if_fail(f->type == CA_SAMPLE_S16NE || f->type == CA_SAMPLE_S16RE, CA_ERROR_STATE);
+
+ if (f->wav)
+ return ca_wav_read_s16le(f->wav, d, n);
+ else
+ return ca_vorbis_read_s16ne(f->wav, d, n);
+}
+
+int ca_sound_file_read_uint8(ca_sound_file *fn, uint8_t *d, unsigned *n) {
+ ca_return_val_if_fail(f, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+ ca_return_val_if_fail(f->wav && !f->vorbis, CA_ERROR_STATE);
+ ca_return_val_if_fail(f->type == CA_SAMPLE_U8, CA_ERROR_STATE);
+
+ if (f->wav)
+ return ca_wav_read_u8(f->wav, d, n);
+}
+
+int ca_sound_file_read_arbitrary(ca_sound_file *f, void *d, size_t *n) {
+ int ret;
+
+ ca_return_val_if_fail(f, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+ ca_return_val_if_fail(f->wav && !f->vorbis, CA_ERROR_STATE);
+
+ switch (f->type) {
+ case CA_SAMPLE_S16NE:
+ case CA_SAMPLE_S16RE: {
+ unsigned k;
+
+ k = *n / sizeof(int16_t);
+ if ((ret = ca_sound_file_read_int16(f, d, &k)) == CA_SUCCESS)
+ *n = k * sizeof(int16_t);
+
+ break;
+ }
+
+ case CA_SAMPLE_S16RE: {
+ unsigned k;
+
+ k = *n;
+ if ((ret = ca_sound_file_read_uint8(f, d, &k)) == CA_SUCCESS)
+ *n = k;
+
+ break;
+ }
+
+ default:
+ ca_assert_not_reached();
+ }
+
+ return ret;
+}
diff --git a/src/read-sound-file.h b/src/read-sound-file.h
new file mode 100644
index 0000000..5ab7e66
--- /dev/null
+++ b/src/read-sound-file.h
@@ -0,0 +1,48 @@
+#ifndef foocareadsoundfilehfoo
+#define foocareadsoundfilehfoo
+
+/* $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/>.
+***/
+
+typedef enum ca_sample_type {
+ CA_SAMPLE_S16NE,
+ CA_SAMPLE_S16RE,
+ CA_SAMPLE_U8
+} ca_sample_type_t;
+
+typedef struct ca_sound_file wa_sound_file;
+
+int ca_sound_file_open(ca_sound_file *f, const char *fn);
+void ca_sound_file_close(ca_sound_file *f);
+
+unsigned ca_sound_file_get_nchannels(ca_sound_file *f);
+unsigned ca_sound_file_get_rate(ca_sound_file *f);
+ca_sample_type_t ca_sound_file_get_sample_type(ca_sound_file *f);
+
+size_t ca_sound_file_get_size(ca_sound_file *f);
+
+int ca_sound_file_read_int16(ca_sound_file *f, int16_t *d, unsigned *n);
+int ca_sound_file_read_uint8(ca_sound_file *f, uint8_t *d, unsigned *n);
+
+int ca_sound_file_read_arbitrary(ca_sound_file *f, void *d, size_t *n);
+
+#endif
diff --git a/src/read-vorbis.c b/src/read-vorbis.c
new file mode 100644
index 0000000..717f851
--- /dev/null
+++ b/src/read-vorbis.c
@@ -0,0 +1,148 @@
+/* $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 "read-vorbis.h"
+
+#define FILE_SIZE_MAX (64U*1024U*1024U)
+
+struct ca_vorbis {
+ OggVorbis_File ovf;
+};
+
+static int convert_error(int or) {
+ switch (or) {
+ case OV_NOSEEK:
+ case OV_BADPACKET:
+ case OV_BADLINK:
+ case OV_FAULT:
+ case OV_EREAD:
+ case OV_HOLE:
+ return CA_ERROR_IO;
+
+ case OV_EIMPL:
+ case OV_EVERSION:
+ case OV_ENOTAUDIO
+ return CA_ERROR_NOT_SUPPORTED;
+
+ case OV_ENOTVORBIS:
+ case OV_EBADHEADER:
+ case OV_EOF:
+ return CA_ERROR_CORRUPT;
+
+ case OV_EINVAL:
+ return CA_ERROR_INVALID;
+
+ default:
+ return CA_ERROR_INTERNAL;
+ }
+}
+
+int ca_vorbis_open(ca_vorbis **_v, FILE *f) {
+ int ret, or;
+ ca_vorbis *v;
+ int64_t n;
+
+ ca_return_val_if_fail(_v, CA_ERROR_INVALID);
+ ca_return_val_if_fail(f, CA_ERROR_INVALID);
+
+ if (!(v = ca_new(ca_vorbis, 1)))
+ return CA_ERROR_OOM;
+
+ if ((or = ov_open(f, &v->ovf, NULL, 0)) < 0) {
+ ret = convert_error(or);
+ goto fail;
+ }
+
+ if ((n = ov_pcm_total(&ovf, -1)) < 0) {
+ ret = convert_error(or);
+ ov_clear(&v->ovf);
+ goto fail;
+ }
+
+ if (n * sizeof(int16_t) > FILE_SIZE_MAX) {
+ ret = CA_ERROR_TOOBIG;
+ ov_clear(&v->ovf);
+ goto fail;
+ }
+
+ *_v = v;
+
+ return CA_SUCCESS;
+
+fail:
+
+ ca_free(v);
+ return ret;
+}
+
+void ca_vorbis_close(ca_vorbis *v) {
+ ca_assert(v);
+
+ ov_clear(&v->ovf);
+ ca_free(v);
+}
+
+unsigned ca_vorbis_get_nchannels(ca_vorbis *v) {
+ vorbis_info *vi;
+ ca_assert(v);
+
+ ca_assert_se(vi = ov_info(&vf, -1));
+
+ return vi->channels;
+}
+
+unsigned ca_vorbis_get_rate(ca_vorbis *v) {
+ vorbis_info *vi;
+ ca_assert(v);
+
+ ca_assert_se(vi = ov_info(&vf, -1));
+
+ return (unsigned) vi->rate;
+}
+
+int ca_vorbis_read_int16ne(ca_vorbis *v, int16_t *d, unsigned *n){
+ long r;
+ int section;
+
+ ca_return_val_if_fail(w, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+
+ r = ov_read(&v->ovf, d, *n * sizeof(float),
+#ifdef WORDS_BIGENDIAN
+ 1,
+#else
+ 0,
+#endif
+ 2, 1, &section);
+
+ if (r < 0)
+ return convert_error(or);
+
+ /* We only read the first section */
+ if (section != 0)
+ return 0;
+
+ *n = (unsigned) r;
+ return CA_SUCCESS;
+}
diff --git a/src/read-vorbis.h b/src/read-vorbis.h
new file mode 100644
index 0000000..5521c09
--- /dev/null
+++ b/src/read-vorbis.h
@@ -0,0 +1,38 @@
+#ifndef foocareadvorbishfoo
+#define foocareadvorbishfoo
+
+/* $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 <stdio.h>
+
+typedef struct ca_vorbis ca_vorbis;
+
+int ca_vorbis_open(ca_vorbis *v, FILE *f);
+void ca_vorbis_close(ca_vorbis *v);
+
+unsigned ca_vorbis_get_nchannels(ca_vorbis *v);
+unsigned ca_vorbis_get_rate(ca_vorbis *v);
+
+int ca_vorbis_read_s16ne(ca_vorbis *v, int16_t *d, unsigned *n);
+
+#endif
diff --git a/src/read-wav.c b/src/read-wav.c
new file mode 100644
index 0000000..1f3d634
--- /dev/null
+++ b/src/read-wav.c
@@ -0,0 +1,247 @@
+/* $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 "read-wav.h"
+
+#define FILE_SIZE_MAX (64U*1024U*1024U)
+
+struct ca_wav {
+ uint32_t data_size;
+ FILE *file;
+
+ unsigned nchannels;
+ unsigned rate;
+ unsigned depth;
+};
+
+static int skip_to_chunk(ca_wav *v, uint32_t id, uint32_t *size) {
+
+ ca_return_val_if_fail(v, CA_ERROR_INVALID);
+ ca_return_val_if_fail(size, CA_ERROR_INVALID);
+
+ for (;;) {
+ uint32_t chunk[2];
+ size_t s;
+
+ if (fread(chunk, sizeof(uint32), CA_ELEMENTSOF(chunk), w->file) != CA_ELEMENTSOF(chunk))
+ goto fail_io;
+
+ s = PA_UINT32_FROM_LE(chunk[1]);
+
+ if (s <= 0 || s >= FILE_SIZE_MAX)
+ return CA_ERROR_TOOBIG;
+
+ if (PA_UINT32_FROM_LE(chunk[0]) == id) {
+ *size = s;
+ break;
+ }
+
+ if (fseek(w->file, s, SEEK_CUR) < 0)
+ return CA_ERROR_SYSTEM;
+ }
+
+ return CA_SUCCESS;
+
+fail_io:
+
+ if (feof(f))
+ return CA_ERROR_CORRUPT;
+ else if (ferror(f))
+ return CA_ERROR_SYSTEM;
+
+ ca_assert_not_reached();
+}
+
+int ca_wav_open(ca_wav **_w, FILE *f) {
+ uint32_t header[3], fmt_chunk[4];
+ int ret;
+ ca_wav *w;
+ uint32_t file_size, fmt_size, data_size;
+
+ ca_return_val_if_fail(_w, CA_ERROR_INVALID);
+ ca_return_val_if_fail(f, CA_ERROR_INVALID);
+
+ if (!(w = ca_new(ca_wav, 1)))
+ return CA_ERROR_OOM;
+
+ v->file = f;
+
+ if (fread(header, sizeof(uint32), CA_ELEMENTSOF(header), f) != CA_ELEMENTSOF(header))
+ goto fail_io;
+
+ if (PA_UINT32_FROM_LE(header[0]) != 0x46464952U ||
+ PA_UINT32_FROM_LE(header[2]) != 0x45564157U) {
+ ret = CA_ERROR_CORRUPT;
+ goto fail;
+ }
+
+ file_size = PA_UINT32_FROM_LE(header[1]);
+
+ if (file_size <= 0 || file_size >= FILE_SIZE_MAX) {
+ ret = CA_ERROR_TOOBIG;
+ goto fail;
+ }
+
+ /* Skip to the fmt chunk */
+ if ((ret = skip_to_chunk(w, 0x20746d66U, &fmt_size)) < 0)
+ goto fail;
+
+ if (fmt_size != 16) {
+ ret = CA_ERROR_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ if (fread(fmt_chunk, sizeof(uint32), CA_ELEMENTSOF(fmt_chunk), f) != CA_ELEMENTSOF(fmt_chunk))
+ goto fail_io;
+
+ if (PA_UINT32_FROM_LE(fmt_chunk[0]) & 0xFFFF != 1) {
+ ret = CA_ERROR_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ w->nchannels = PA_UINT32_FROM_LE(fmt_chunk[0]) >> 16;
+ w->rate = PA_UINT32_FROM_LE(fmt_chunk[1]);
+ w->depth = PA_UINT32_FROM_LE(fmt_chunk[3]) >> 16;
+
+ if (w->nchannels <= 0 || w->nrate <= 0) {
+ ret = CA_ERROR_CORRUPT;
+ goto fail;
+ }
+
+ if (w->depth != 16 && w->depth != 8) {
+ ret = CA_ERROR_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ /* Skip to the data chunk */
+ if ((ret = skip_to_chunk(w, 0x61746164U, &w->data_size)) < 0)
+ goto fail;
+
+ if ((w->data_size % (w->depth/8)) != 0) {
+ ret = CA_ERROR_CORRUPT;
+ goto fail;
+ }
+
+ *_w = w;
+
+ return PA_SUCCESS;
+
+fail_io:
+
+ if (feof(f))
+ ret = CA_ERROR_CORRUPT;
+ else if (ferror(f))
+ ret = CA_ERROR_SYSTEM;
+ else
+ ca_assert_not_reached();
+
+fail:
+
+ ca_free(w);
+
+ return ret;
+}
+
+void ca_wav_close(ca_wav *w) {
+ ca_assert(w);
+
+ fclose(w->file);
+ ca_free(w);
+}
+
+unsigned ca_wav_get_nchannels(ca_wav *w) {
+ ca_assert(w);
+
+ return w->nchannels;
+}
+
+unsigned ca_wav_get_rate(ca_wav *w) {
+ ca_assert(w);
+
+ return w->rate;
+}
+
+ca_sample_type_t ca_wav_get_sample_type(ca_wav *f) {
+ ca_assert(w);
+
+ return w->depth == 16 ?
+#ifdef WORDS_BIGENDIAN
+ CA_SAMPLE_S16RE
+#else
+ CA_SAMPLE_S16NE
+#endif
+ : CA_SAMPLE_U8;
+}
+
+int ca_wav_read_s16le(ca_wav *w, int16_t *d, unsigned *n) {
+ unsigned remaining;
+
+ ca_return_val_if_fail(w, CA_ERROR_INVALID);
+ ca_return_val_if_fail(w->depth == 16, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+
+ remaining = w->data_size / sizeof(int16_t);
+
+ if (*n > remaining)
+ *n = remaining;
+
+ if (*n > 0) {
+ *n = fread(d, sizeof(int16_t), *n, w->file);
+
+ if (*n <= 0 && ferror(w->file))
+ return CA_ERROR_SYSTEM;
+
+ ca_assert(w->data_size >= *n * sizeof(int16_t));
+ w->data_size -= *n * sizeof(int16_t);
+ }
+
+ return CA_SUCCESS;
+}
+
+int ca_wav_read_u(ca_wav *w, uint8_t *d, unsigned *n) {
+ unsigned remaining;
+
+ ca_return_val_if_fail(w, CA_ERROR_INVALID);
+ ca_return_val_if_fail(w->depth == 8, CA_ERROR_INVALID);
+ ca_return_val_if_fail(d, CA_ERROR_INVALID);
+ ca_return_val_if_fail(n, CA_ERROR_INVALID);
+ ca_return_val_if_fail(*n > 0, CA_ERROR_INVALID);
+
+ remaining = w->data_size / sizeof(uint8_t);
+
+ if (*n > remaining)
+ *n = remaining;
+
+ if (*n > 0) {
+ *n = fread(d, sizeof(uint8_t), *n, w->file);
+
+ if (*n <= 0 && ferror(w->file))
+ return CA_ERROR_SYSTEM;
+
+ ca_assert(w->data_size >= *n * sizeof(int16_t));
+ w->data_size -= *n * sizeof(uint8_t);
+ }
+
+ return CA_SUCCESS;
+}
diff --git a/src/read-wav.h b/src/read-wav.h
new file mode 100644
index 0000000..8344cc9
--- /dev/null
+++ b/src/read-wav.h
@@ -0,0 +1,42 @@
+#ifndef foocareadwavhfoo
+#define foocareadwavhfoo
+
+/* $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 <stdio.h>
+
+#include "read-sound-file.h"
+
+typedef struct ca_wav ca_wav;
+
+int ca_wav_open(ca_wav **v, FILE *f);
+void ca_wav_close(ca_wav *f);
+
+unsigned ca_wav_get_nchannels(ca_wav *f);
+unsigned ca_wav_get_rate(ca_wav *f);
+ca_sample_type ca_wav_get_sample_type(ca_wav *f);
+
+int ca_wav_read_u8(ca_wav *f, uint8_t *d, unsigned *n);
+int ca_wav_read_s16le(ca_wav *f, int16_t *d, unsigned *n);
+
+#endif
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);
+}
diff --git a/src/sound-theme-spec.h b/src/sound-theme-spec.h
new file mode 100644
index 0000000..527ad11
--- /dev/null
+++ b/src/sound-theme-spec.h
@@ -0,0 +1,34 @@
+#ifndef foocasoundthemespechfoo
+#define foocasoundthemespechfoo
+
+/* $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 "read-sound-file.h"
+#include "proplist.h"
+
+typedef struct ca_theme_data ca_theme_data;
+
+int ca_lookup_sound(ca_sound_file **f, ca_theme_data **t, ca_proplist *p);
+void ca_theme_data_free(ca_theme_data *t);
+
+#endif
diff --git a/src/test-gtk.c b/src/test-gtk.c
new file mode 100644
index 0000000..71d8aac
--- /dev/null
+++ b/src/test-gtk.c
@@ -0,0 +1,18 @@
+
+
+static void trigger(GtkWidget *w) {
+
+ ca_context_play(
+ gca_default_context(),
+ CA_PROP_EVENT_ID, "clicked",
+ GCA_PROPS_FOR_WIDGET(w),
+ GCA_PROPS_FOR_MOUSE_EVENT(e),
+ GCA_PROPS_FOR_APP(),
+ NULL);
+
+}
+
+int main(int argc, char *argv[]) {
+
+ ca_
+}
diff --git a/src/test.c b/src/test.c
new file mode 100644
index 0000000..0fd30e0
--- /dev/null
+++ b/src/test.c
@@ -0,0 +1,79 @@
+/* $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 <unistd.h>
+
+#include "canberra.h"
+
+int main(int argc, char *argv[]) {
+ ca_context_t *c;
+
+ int id = 4711;
+
+ ca_context_new(&c);
+
+ /* Initialize a few meta variables for the following play()
+ * calls. They stay valid until they are overwritten with
+ * ca_context_set() again. */
+ ca_context_change_props(c,
+ CA_PROP_APPLICATION_NAME, "An example",
+ CA_PROP_APPLICATION_ID, "org.freedesktop.libcanberra.Test",
+ CA_PROP_MEDIA_LANGUAGE, "de_DE",
+ CA_PROP_EVENT_X11_DISPLAY, getenv("DISPLAY"),
+ NULL);
+
+ /* .. */
+
+ ca_context_open(c);
+
+
+ /* Signal a sound event. The meta data passed here overwrites the
+ * data set in any previous ca_context_set() calls. */
+ ca_context_play(c, id,
+ CA_PROP_EVENT_ID, "click-event",
+ CA_PROP_MEDIA_FILENAME, "/usr/share/sounds/foo.wav",
+ CA_PROP_MEDIA_NAME, "Button has been clicked",
+ CA_PROP_MEDIA_ICON_NAME, "clicked",
+ NULL);
+
+ /* .. */
+
+ ca_context_play(c, id,
+ CA_PROP_EVENT_ID, "logout",
+ CA_PROP_MEDIA_FILENAME, "/usr/share/sounds/bar.wav",
+ CA_PROP_MEDIA_NAME, "User has logged of from session",
+ CA_PROP_MEDIA_LANGUAGE, "en_EN",
+ NULL);
+
+ /* .. */
+
+ sleep(1);
+
+ /* Stops both sounds */
+ ca_context_cancel(c, id);
+
+ /* .. */
+
+ ca_context_destroy(c);
+
+ return 0;
+}