From d7fd6a45e50475cddf0b8bad8baab01b33cf3c1f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 26 May 2008 22:00:19 +0000 Subject: move sources to src/ subdir git-svn-id: file:///home/lennart/svn/public/libcanberra/trunk@12 01b60673-d06a-42c0-afdd-89cb8e0f78ac --- src/canberra.c | 1 + src/canberra.h | 210 ++++++++++++ src/common.c | 380 +++++++++++++++++++++ src/common.h | 50 +++ src/driver.h | 38 +++ src/llist.h | 108 ++++++ src/macro.h | 239 +++++++++++++ src/malloc.h | 41 +++ src/mutex-posix.c | 80 +++++ src/mutex.h | 37 ++ src/proplist.c | 317 +++++++++++++++++ src/proplist.h | 53 +++ src/pulse.c | 903 +++++++++++++++++++++++++++++++++++++++++++++++++ src/read-sound-file.c | 175 ++++++++++ src/read-sound-file.h | 48 +++ src/read-vorbis.c | 148 ++++++++ src/read-vorbis.h | 38 +++ src/read-wav.c | 247 ++++++++++++++ src/read-wav.h | 42 +++ src/sound-theme-spec.c | 632 ++++++++++++++++++++++++++++++++++ src/sound-theme-spec.h | 34 ++ src/test-gtk.c | 18 + src/test.c | 79 +++++ 23 files changed, 3918 insertions(+) create mode 100644 src/canberra.c create mode 100644 src/canberra.h create mode 100644 src/common.c create mode 100644 src/common.h create mode 100644 src/driver.h create mode 100644 src/llist.h create mode 100644 src/macro.h create mode 100644 src/malloc.h create mode 100644 src/mutex-posix.c create mode 100644 src/mutex.h create mode 100644 src/proplist.c create mode 100644 src/proplist.h create mode 100644 src/pulse.c create mode 100644 src/read-sound-file.c create mode 100644 src/read-sound-file.h create mode 100644 src/read-vorbis.c create mode 100644 src/read-vorbis.h create mode 100644 src/read-wav.c create mode 100644 src/read-wav.h create mode 100644 src/sound-theme-spec.c create mode 100644 src/sound-theme-spec.h create mode 100644 src/test-gtk.c create mode 100644 src/test.c (limited to 'src') 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 + . +***/ + +#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. + +*/ + +#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 + . +***/ + +#include + +#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 + . +***/ + +#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 + . +***/ + +#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 + . +***/ + +#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 + . +***/ + +#include +#include + +#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 +#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 + . +***/ + +#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/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 + . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + . +***/ + +#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 + . +***/ + +#include + +#include "canberra.h" +#include "proplist.h" +#include "macro.h" +#include "malloc.h" + +#ifdef HAVE_CONFIG_H +#include +#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 + . +***/ + +#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 + . +***/ + +/* 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 +#include +#include + +#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 + . +***/ + +#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 + . +***/ + +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 + . +***/ + +#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, §ion); + + 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 + . +***/ + +#include + +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 + . +***/ + +#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 + . +***/ + +#include + +#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 + . +***/ + +#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 + . +***/ + +#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 + . +***/ + +#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_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; +} -- cgit