From 1559476e75534b4b4896a9fdb08f78af0ad7882e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 11 Apr 2008 15:36:25 +0000 Subject: commit what i prepared a while back git-svn-id: file:///home/lennart/svn/public/libcanberra/trunk@5 01b60673-d06a-42c0-afdd-89cb8e0f78ac --- canberra.c | 1 + canberra.h | 225 +++++++++++++++++++++++--------------- common.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ common.h | 27 +++++ driver.h | 16 +++ macro.h | 89 +++++++++++++++ malloc.h | 20 ++++ pulse.c | 361 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.c | 59 ++++++++++ 9 files changed, 1036 insertions(+), 85 deletions(-) create mode 100644 canberra.c create mode 100644 common.c create mode 100644 common.h create mode 100644 driver.h create mode 100644 macro.h create mode 100644 malloc.h create mode 100644 pulse.c create mode 100644 test.c diff --git a/canberra.c b/canberra.c new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/canberra.c @@ -0,0 +1 @@ + diff --git a/canberra.h b/canberra.h index a2ae126..8482cf3 100644 --- a/canberra.h +++ b/canberra.h @@ -1,85 +1,140 @@ - - - -typedef struct cbr_context cbr_context_t; - -typedef enum { - CBR_META_NULL = -1, - CBR_META_NAME = 0, - CBR_META_IDENTIFIER, - CBR_META_CLIENT_NAME, - CBR_META_CLIENT_IDENTIFIER, - CBR_META_SOUND_FILE, - CBR_META_VOLUME, - CBR_META_ROLE, - CBR_META_X11_DISPLAY, - CBR_META_X11_WINDOW, - CBR_META_LANGUAGE, - CBR_META_ICON_NAME, - CBR_META_ICON_FILE, - CBR_META_PRIORITY, - - _CBR_META_MAX, -} cbr_meta_t; - -cbr_context_t cbr_context_new(const char *client_name); -int cbr_context_free(cbr_context_t *c); -int cbr_context_set(cbr_context_t *c, ...); -int cbr_context_set_arbitrary(cbr_context_t *c, cbr_meta_t m, const void *c, size_t len); -int cbr_context_play(cbr_context_t *c, int id, ...); -int cbr_context_cancel(cbr_context_t *c, int id); -int cbr_context_cache(cbr_context_t *c, ...); - - -int main(int argc, char *argv[]) { - - cbr_context_t *c; - - int id = 4711; - - c = cbr_context_new("Mozilla Firefox"); - - /* Initialize a few meta variables for the following play() - * calls. They stay valid until they are overwritten with - * cbr_context_set() again. */ - cbr_context_set(c, - CBR_META_VOLUME, "-20", /* -20 dB */ - CBR_META_ROLE, "event", - CBR_META_X11_DISPLAY, getenv("DISPLAY"), - CBR_META_LANGUAGE, "de_DE", - -1); - - /* .. */ - - cbr_context_set_arbitrary(c, CBR_META_ICON, "some png data here", 4711); - - - /* Signal a sound event. The meta data passed here overwrites the - * data set in any previous cbr_context_set() calls. */ - cbr_context_play(c, id, - CBR_META_NAME, "click-event", - CBR_META_SOUND_FILE, "/usr/share/sounds/foo.wav", - CBR_META_DESCRIPTION, "Button has been clicked", - CBR_META_ICON_NAME, "clicked", - -1); - - /* .. */ - - cbr_context_play(c, id, - CBR_META_NAME, "logout", - CBR_META_SOUND_FILE_WAV, "/usr/share/sounds/bar.wav", - CBR_META_DESCRIPTION, "User has logged of from session", - CBR_META_ROLE, "session", - -1); - - /* .. */ - - /* Stops both sounds */ - cbr_context_cancel(c, id); - - /* .. */ - - cbr_context_destroy(c); - - return 0; -} +#ifndef foocanberrahfoo +#define foocanberrahfoo + +#include +#include +#include + +/* + + 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. + +*/ + +/* Context object */ +typedef struct ca_context ca_context_t; + +/** Context, event, and playback properties */ +#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_X11_DISPLAY "event.x11.display" +#define CA_PROP_EVENT_X11_XID "event.x11.xid" +#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_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_CONTROL_CACHE "control.cache" /* permanent, volatile, never */ +#define CA_PROP_CONTROL_VOLUME "control.volume" /* decibel */ + +/** Playback completion event callback */ +typedef void ca_finish_callback_t(ca_context *c, uint32_t id, void *userdata); + +/** Error codes */ +enum { + CA_SUCCESS = 0, + CA_ERROR_NOT_SUPPORTED = -1, + CA_ERROR_INVALID = -2, + CA_ERROR_STATE = -3, + CA_ERROR_OOM = -4, + CA_ERROR_NO_DRIVER = -5, + CA_ERROR_SYSTEM = -6, + _CA_ERROR_MAX = -7 +}; + +/** Create an (unconnected) context object */ +int ca_context_create(ca_context_t **c); + +/** 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_t *c); + +/** Destroy a (connected or unconnected) cntext object. */ +int ca_context_destroy(ca_context_t *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_t *c, ...) CA_GCC_SENTINEL; + +/** Write an arbitrary data property to the context object. */ +int ca_context_change_prop(ca_context_t *c, const char *key, const void *data, size_t nbytes); + +/** Remove a property from the context object again. */ +int ca_context_remove_prop(ca_context_t *c, ...) CA_GCC_SENTINEL; + +/** 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. If the requested sound is not cached in the server yet + * this call might result in the sample being uploaded temporarily or + * permanently. */ +int ca_context_play(ca_context_t *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_with_callback(ca_context_t *c, uint32_t id, ca_finish_callback_t cb, void *userdata, ...) CA_GCC_SENTINEL; + +/** Cancel one or more event sounds that have been started via + * ca_context_play(). */ +int ca_context_cancel(ca_context_t *c, uint32_t id); + +/** Upload the specified sample into the server and attach the + * specified properties to it */ +int ca_context_cache(ca_context_t *c, ...) CA_GCC_SENTINEL; + +/** Return a human readable error string */ +const char *ca_strerror(int code); + +#endif diff --git a/common.c b/common.c new file mode 100644 index 0000000..9ae9d8e --- /dev/null +++ b/common.c @@ -0,0 +1,323 @@ +#include "canberra.h" +#include "common.h" + +int ca_context_create(ca_context_t **c) { + ca_return_val_if_fail(c, CA_ERROR_INVALID); + + if (!(*c = ca_new0(ca_context_t, 1))) + return CA_ERROR_OOM; + + return CA_SUCCESS; +} + +int ca_context_destroy(ca_context_t *c) { + int ret; + unsigned i; + ca_prop *p, *n; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + + ret = driver_destroy(c); + + for (p = c->first_item; p; p = n) { + n = p->next_item; + ca_free(p); + } + + ca_free(c); + + return ret; +} + +int ca_context_open(ca_context_t *c) { + int ret; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(!c->opened, CA_ERROR_STATE); + + if ((ret = driver_open(c)) == 0) + c->opened = TRUE; + + return ret; +} + +int ca_context_sets(ca_context_t *c, ...) { + va_list ap; + int ret = CA_SUCCESS; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + + va_start(ap, c); + + for (;;) { + const char *key, *value; + int ret; + + if (!(key = v_arg(ap, const char*))) + break; + + if (!(value = v_arg(ap, const char *))) { + ret = CA_ERROR_INVALID; + break; + } + + if ((ret = ca_context_set(c, key, value, strlen(value)+1)) < 0) + break; + } + + va_end(ap); + + return ret; +} + +static int _unset(ca_context_t *c, const char *key) { + ca_prop *p, *np; + unsigned i; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(key, CA_ERROR_INVALID); + + i = calc_hash(key) % N_HASHTABLE; + + np = NULL; + for (p = c->props[i]; p; np = p, p = p->next_in_slot) + if (strcmp(p->key, key) == 0) + break; + + if (p) { + if (np) + np->next_in_slot = p->next_in_slot; + else + c->props[i] = p->next_in_slot; + + if (p->prev_item) + p->prev_item->next_item = p->next_item; + else + c->first_item = p->next_item; + + if (p->next_item) + p->next_item->prev_item = p->prev_item; + + ca_free(p); + } +} + +int ca_context_set(ca_context_t *c, const char *key, const void *data, size_t nbytes) { + int ret; + ca_prop *p; + char *k; + + ca_return_val_if_fail(c, 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 (!(p = ca_malloc(CA_ALIGN(sizeof(ca_prop)) + nbytes))) { + ca_free(k); + return CA_ERROR_OOM; + } + + if ((ret = _unset(c, key)) < 0) { + ca_free(p); + ca_free(k); + return ret; + } + + p->key = k; + p->nbytes = nbytes; + memcpy(CA_PROP_DATA(p), data, nbytes); + + i = calc_hash(key) % N_HASHTABLE; + + p->next_in_slot = c->props[i]; + c->props[i] = p; + + p->prev_item = NULL; + p->next_item = c->first_item; + c->first_item = p; + + if (c->opened) + if ((ret = driver_set(c, key, data, nbytes)) < 0) + return ret; + + return CA_SUCCESS; +} + +int ca_context_unset(ca_context *c, ...) { + int ret = CA_SUCCESS; + va_list ap; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(key, CA_ERROR_INVALID); + + va_start(ap, c); + + for (;;) { + const char *key; + + if (!(key = v_arg(ap, const char*))) + break; + + if (c->opened) { + if ((ret = driver_unset(c, key)) < 0) + break; + } + + if ((ret = _unset(c, key)) < 0) + break; + } + + va_end(ap); + + return ret; +} + +/* Not exported */ +ca_prop* ca_context_get(ca_context *c, const char *key) { + ca_prop *p; + unsigned i; + + ca_return_val_if_fail(c, NULL); + ca_return_val_if_fail(key, NULL); + + i = calc_hash(key) % N_HASHTABLE; + + for (p = c->props[i]; p; p = p->next_in_slot) + if (strcmp(p->key, key) == 0) + return p; + + return NULL; +} + +/* Not exported */ +const char* ca_context_gets(ca_context *c, const char *key) { + ca_prop *p; + + ca_return_val_if_fail(c, NULL); + ca_return_val_if_fail(key, NULL); + + if (!(p = ca_context_get(c, key))) + return NULL; + + if (memchr(CA_PROP_DATA(p), 0, p->nbytes)) + return CA_PROP_DATA(p); + + return NULL; +} + +int ca_context_play(ca_context_t *c, uint32_t id, ...) { + int ret; + va_list ap; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(key, CA_ERROR_INVALID); + + if (!c->opened) + if ((ret = ca_context_open(c)) < 0) + return ret; + + ca_assert(c->opened); + + /* make sure event.id is set */ + + va_start(ap, c); + for (;;) { + const char *key, *value; + + if (!(key = va_arg(ap, const char *))) + break; + + if (!(value = va_arg(ap, const char *))) { + va_end(ap); + return CA_ERROR_INVALID; + } + + found = found || strcmp(key, CA_PROP_EVENT_ID) == 0; + } + va_end(ap); + + found = found || ca_context_gets(c, CA_PROP_EVENT_ID); + + if (!found) + return CA_ERROR_INVALID; + + va_start(ap, id); + ret = driver_play(c, id, ap); + va_end(ap); + + return ret; +} + +int ca_context_cancel(ca_context_t *c, uint32_ id) { + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(c->opened, CA_ERROR_STATE); + + return driver_cancel(c, id); +} + +int ca_context_cache(ca_context_t *c, ...) { + int ret; + va_list ap; + ca_bool_t found = FALSE; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + + if (!c->opened) + if ((ret = ca_context_open(c)) < 0) + return ret; + + ca_assert(c->opened); + + /* make sure event.id is set */ + + va_start(ap, c); + for (;;) { + const char *key, *value; + + if (!(key = va_arg(ap, const char *))) + break; + + if (!(value = va_arg(ap, const char *))) { + va_end(ap); + return CA_ERROR_INVALID; + } + + found = found || strcmp(key, CA_PROP_EVENT_ID) == 0; + } + va_end(ap); + + found = found || ca_context_gets(c, CA_PROP_EVENT_ID); + + if (!found) + return CA_ERROR_INVALID; + + va_start(ap, c); + ret = driver_cache(c, ap); + va_end(ap); + + 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 > _SA_ERROR_MAX, NULL); + + return error_table[-code]; +} + +#endif diff --git a/common.h b/common.h new file mode 100644 index 0000000..6d4ca55 --- /dev/null +++ b/common.h @@ -0,0 +1,27 @@ +#ifndef foocanberracommonh +#define foocanberracommonh + +#include "canberra.h" + +#define N_HASHTABLE 39 + +typedef struct ca_prop { + char *key; + size_t nbytes; + struct ca_prop *next_in_slot, *next_item, *prev_item; +} ca_prop; + +struct ca_context { + ca_bool_t opened; + ca_prop *prop_hashtable[N_HASHTABLE]; + ca_prop *first_item; + void *private; +}; + +#define CA_PROP_DATA(p) ((void*) ((char*) (p) + CA_ALIGN(sizeof(ca_prop)))) + +ca_prop* ca_context_get(ca_context *c, const char *key); +const char* ca_context_gets(ca_context *c, const char *key); + + +#endif diff --git a/driver.h b/driver.h new file mode 100644 index 0000000..dc2e8c5 --- /dev/null +++ b/driver.h @@ -0,0 +1,16 @@ +#ifndef foocanberradriverhfoo +#define foocanberradriverhfoo + +#include "canberra.h" + +int driver_open(ca_context *c); +int driver_destroy(ca_context *c); + +int driver_set(ca_context *c, const char *key, const void* data, size_t nbytes); +int driver_unset(ca_context *c, const char *key); + +int driver_play(ca_context *c, uint32_t id, ca_notify_cb_t cb, void *userdata, va_list ap); +int driver_cancel(ca_context *c, uint32_t id); +int driver_cache(ca_context *c, va_list ap); + +#endif diff --git a/macro.h b/macro.h new file mode 100644 index 0000000..26dca20 --- /dev/null +++ b/macro.h @@ -0,0 +1,89 @@ +#ifndef foocanberramacrohfoo +#define foocanberramacrohfoo + +#include +#include + +#ifdef __GNUC__ +#define CA_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#define CA_PRETTY_FUNCTION "" +#endif + +#define ca_return_if_fail(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: Assertion <%s> failed.\n", CA_PRETTY_FUNCTION, #expr ); \ + return; \ + } \ + } while(0) + +#define ca_return_val_if_fail(expr, val) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: Assertion <%s> failed.\n", CA_PRETTY_FUNCTION, #expr ); \ + return (val); \ + } \ + } while(0) + +#define ca_assert assert + +/* An assert which guarantees side effects of x */ +#ifdef NDEBUG +#define ca_assert_se(x) x +#else +#define ca_assert_se(x) ca_assert(x) +#endif + +#define ca_assert_not_reached() ca_assert(!"Should not be reached.") + +#define ca_assert_success(x) do { \ + int _r = (x); \ + ca_assert(_r == 0); \ + } while(0) + +#define CA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef CLAMP +#define 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; + +#endif diff --git a/malloc.h b/malloc.h new file mode 100644 index 0000000..f87b85b --- /dev/null +++ b/malloc.h @@ -0,0 +1,20 @@ +#ifndef foosydneymallochfoo +#define foocanberramallochfoo + +#include +#include + +#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/pulse.c b/pulse.c new file mode 100644 index 0000000..2abb4a5 --- /dev/null +++ b/pulse.c @@ -0,0 +1,361 @@ +#include + +#include "canberra.h" +#include "common." +#include "driver.h" + +struct private { + pa_threaded_mainloop *mainloop; + pa_context *context; +}; + +#define PRIVATE(c) ((struct private *) ((c)->private) + +int driver_open(ca_context *c) { + pa_proplist *l; + struct private *p; + ca_prop *i; + ca_return_val_if_fail(c, PA_ERROR_INVALID); + + 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; + } + + l = pa_proplist_new(); + + for (i = c->first_item; i; i = i->next_item) + if (pa_proplist_put(l, i->key, CA_PROP_DATA(i), i->nbytes) < 0) { + driver_destroy(c); + pa_proplist_free(l); + return PA_ERROR_INVALID; + } + + 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); + + 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; + } + + return PA_SUCCESS; +} + +int driver_destroy(ca_context *c) { + ca_return_val_if_fail(c, PA_ERROR_INVALID); + + 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); + + ca_free(p); +} + +int driver_prop_put(ca_context *c, const char *key, const void* data, size_t nbytes) { + 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(key, PA_ERROR_INVALID); + + p = PRIVATE(c); + + ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE); + ca_return_val_if_fail(p->context, PA_ERROR_STATE); + + l = pa_proplist_new(); + + if (pa_proplist_put(l, key, data, nbytes) < 0) { + pa_proplist_free(l); + return PA_ERROR_INVALID; + } + + 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); + + return ret; +} + +int driver_prop_unset(ca_context *c, const char *key) { + struct private *p; + pa_operation *o; + const char *a[2]; + int ret = CA_SUCCESS; + + ca_return_val_if_fail(c, PA_ERROR_INVALID); + ca_return_val_if_fail(key, PA_ERROR_INVALID); + + p = PRIVATE(c); + + ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE); + ca_return_val_if_fail(p->context, PA_ERROR_STATE); + + a[0] = key; + a[1] = NULL; + + pa_threaded_mainloop_lock(p->mainloop); + + /* We start these asynchronously and don't care about the return + * value */ + + if (!(o = pa_context_proplist_remove(p->context, a, NULL, NULL))) + ret = translate_error(pa_context_errno(p->context)); + else + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(p->mainloop); + + return ret; +} + +static pa_proplist* proplist_unroll(va_list ap) { + pa_proplist *l; + + l = pa_proplist_new(); + + for (;;) { + const char *key, *value; + + if (!(key = va_arg(ap, const char*))) + break; + + if (!(value = va_arg(ap, const char *))) { + pa_proplist_free(l); + return NULL; + } + + if (pa_proplist_puts(l, key, value) < 0) { + pa_proplist_free(l); + return NULL; + } + } + + return l; +} + +int driver_play(ca_context *c, uint32_t id, va_list ap) { + struct private *p; + pa_proplist *l; + const char *name; + ca_bool_t played = FALSE; + + ca_return_val_if_fail(c, PA_ERROR_INVALID); + + p = PRIVATE(c); + + ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE); + ca_return_val_if_fail(p->context, PA_ERROR_STATE); + + if (!(l = proplist_unroll(ap))) + return PA_ERROR_INVALID; + + if (!(name = pa_proplist_gets(l, CA_PROP_EVENT_ID))) + if (!(name = ca_context_gets(l, CA_PROP_EVENT_ID))) { + pa_proplist_free(l); + return PA_ERROR_INVALID; + } + + pa_threaded_mainloop_lock(p->mainloop); + + if ((o = pa_context_play_sample_with_proplist(p->context, name, NULL, PA_VOLUME_NORM, l, id, play_success_cb, c))) { + + while (pa_operation_get_state(o) != OPERATION_DONE) + pa_threaded_mainloop_wait(m); + + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + if (played) + return CA_SUCCESS; + + va_copy(aq, ap); + ret = file_open(&f, c, aq); + va_end(aq); + + if (ret < 0) { + pa_proplist_free(l); + return ret; + } + + pa_threaded_mainloop_lock(p->mainloop); + + s = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l); + pa_proplist_free(l); + + if (!s) { + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + pa_stream_set_state_callback(p->stream, stream_state_cb, c); + pa_stream_set_write_callback(p->stream, stream_request_cb, c); + + if (pa_stream_connect_playback(s, NULL, NULL, 0, NULL, NULL) < 0) { + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_stream_disconnect(s); + pa_stream_unref(s); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + while (!done) { + + if (pa_stream_get_state(s) != PA_STREAM_READY || + pa_context_get_state(c) != PA_CONTEXT_READY) { + + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_stream_disconnect(s); + pa_stream_unref(s); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_stream_disconnect(s); + pa_stream_unref(s); + + return CA_SUCCESS; + +} + +int driver_cancel(ca_context *c, uint32_t id) { + ca_return_val_if_fail(c, PA_ERROR_INVALID); + +} + +int driver_cache(ca_context *c, va_list ap) { + struct private *p; + pa_proplist *l; + ca_file *f; + int ret; + va_list aq; + pa_stream *s; + const char *name; + + ca_return_val_if_fail(c, PA_ERROR_INVALID); + + p = PRIVATE(c); + + ca_return_val_if_fail(p->mainloop, PA_ERROR_STATE); + ca_return_val_if_fail(p->context, PA_ERROR_STATE); + + if (!(l = proplist_unroll(ap))) + return PA_ERROR_INVALID; + + if (!(name = pa_proplist_gets(l, CA_PROP_EVENT_ID))) + if (!(name = ca_context_gets(l, CA_PROP_EVENT_ID))) { + pa_proplist_free(l); + return PA_ERROR_INVALID; + } + + va_copy(aq, ap); + ret = file_open(&f, c, aq); + va_end(aq); + + if (ret < 0) { + pa_proplist_free(l); + return ret; + } + + pa_threaded_mainloop_lock(p->mainloop); + + s = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l); + pa_proplist_free(l); + + if (!s) { + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + pa_stream_set_state_callback(p->stream, stream_state_cb, c); + pa_stream_set_write_callback(p->stream, stream_request_cb, c); + + if (pa_stream_connect_upload(s, f->nbytes) < 0) { + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_stream_disconnect(s); + pa_stream_unref(s); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + while (!done) { + + if (pa_stream_get_state(s) != PA_STREAM_READY || + pa_context_get_state(c) != PA_CONTEXT_READY) { + + ret = translate_error(pa_context_errno(p->context)); + file_close(f); + pa_stream_disconnect(s); + pa_stream_unref(s); + pa_threaded_mainloop_unlock(p->mainloop); + return ret; + } + + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_stream_disconnect(s); + pa_stream_unref(s); + + return CA_SUCCESS; +} diff --git a/test.c b/test.c new file mode 100644 index 0000000..8542ebd --- /dev/null +++ b/test.c @@ -0,0 +1,59 @@ +#include + +#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_puts(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_put(c, CA_PROP_MEDIA_ICON, "some png data here", 23); + + 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; +} -- cgit