diff options
| -rw-r--r-- | canberra.c | 1 | ||||
| -rw-r--r-- | canberra.h | 225 | ||||
| -rw-r--r-- | common.c | 323 | ||||
| -rw-r--r-- | common.h | 27 | ||||
| -rw-r--r-- | driver.h | 16 | ||||
| -rw-r--r-- | macro.h | 89 | ||||
| -rw-r--r-- | malloc.h | 20 | ||||
| -rw-r--r-- | pulse.c | 361 | ||||
| -rw-r--r-- | test.c | 59 | 
9 files changed, 1036 insertions, 85 deletions
diff --git a/canberra.c b/canberra.c new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/canberra.c @@ -0,0 +1 @@ + @@ -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 <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. + +*/ + +/* 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 @@ -0,0 +1,89 @@ +#ifndef foocanberramacrohfoo +#define foocanberramacrohfoo + +#include <stdio.h> +#include <assert.h> + +#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 <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 +~ @@ -0,0 +1,361 @@ +#include <pulse/thread-mainloop.h> + +#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; +} @@ -0,0 +1,59 @@ +#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_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; +}  | 
