summaryrefslogtreecommitdiffstats
path: root/src/polyp
diff options
context:
space:
mode:
Diffstat (limited to 'src/polyp')
-rw-r--r--src/polyp/cdecl.h42
-rw-r--r--src/polyp/channelmap.c202
-rw-r--r--src/polyp/channelmap.h98
-rw-r--r--src/polyp/client-conf-x11.c91
-rw-r--r--src/polyp/client-conf-x11.h31
-rw-r--r--src/polyp/client-conf.c191
-rw-r--r--src/polyp/client-conf.h52
-rw-r--r--src/polyp/glib-mainloop.c538
-rw-r--r--src/polyp/glib-mainloop.h55
-rw-r--r--src/polyp/glib12-mainloop.c500
-rw-r--r--src/polyp/mainloop-api.c68
-rw-r--r--src/polyp/mainloop-api.h120
-rw-r--r--src/polyp/mainloop-signal.c267
-rw-r--r--src/polyp/mainloop-signal.h60
-rw-r--r--src/polyp/mainloop.c812
-rw-r--r--src/polyp/mainloop.h90
-rw-r--r--src/polyp/polyplib-browser.c312
-rw-r--r--src/polyp/polyplib-browser.h65
-rw-r--r--src/polyp/polyplib-context.c871
-rw-r--r--src/polyp/polyplib-context.h117
-rw-r--r--src/polyp/polyplib-def.h213
-rw-r--r--src/polyp/polyplib-error.c54
-rw-r--r--src/polyp/polyplib-error.h38
-rw-r--r--src/polyp/polyplib-internal.h154
-rw-r--r--src/polyp/polyplib-introspect.c1003
-rw-r--r--src/polyp/polyplib-introspect.h279
-rw-r--r--src/polyp/polyplib-operation.c103
-rw-r--r--src/polyp/polyplib-operation.h51
-rw-r--r--src/polyp/polyplib-scache.c127
-rw-r--r--src/polyp/polyplib-scache.h50
-rw-r--r--src/polyp/polyplib-simple.c393
-rw-r--r--src/polyp/polyplib-simple.h80
-rw-r--r--src/polyp/polyplib-stream.c807
-rw-r--r--src/polyp/polyplib-stream.h181
-rw-r--r--src/polyp/polyplib-subscribe.c81
-rw-r--r--src/polyp/polyplib-subscribe.h47
-rw-r--r--src/polyp/polyplib-version.h.in47
-rw-r--r--src/polyp/polyplib.h86
-rw-r--r--src/polyp/sample.c149
-rw-r--r--src/polyp/sample.h120
-rw-r--r--src/polyp/volume.c176
-rw-r--r--src/polyp/volume.h107
42 files changed, 8928 insertions, 0 deletions
diff --git a/src/polyp/cdecl.h b/src/polyp/cdecl.h
new file mode 100644
index 00000000..d51ae026
--- /dev/null
+++ b/src/polyp/cdecl.h
@@ -0,0 +1,42 @@
+#ifndef foocdeclhfoo
+#define foocdeclhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/** \file
+ * C++ compatibility support */
+
+#ifdef __cplusplus
+/** If using C++ this macro enables C mode, otherwise does nothing */
+#define PA_C_DECL_BEGIN extern "C" {
+/** If using C++ this macros switches back to C++ mode, otherwise does nothing */
+#define PA_C_DECL_END }
+
+#else
+/** If using C++ this macro enables C mode, otherwise does nothing */
+#define PA_C_DECL_BEGIN
+/** If using C++ this macros switches back to C++ mode, otherwise does nothing */
+#define PA_C_DECL_END
+
+#endif
+
+#endif
diff --git a/src/polyp/channelmap.c b/src/polyp/channelmap.c
new file mode 100644
index 00000000..7bfd21e6
--- /dev/null
+++ b/src/polyp/channelmap.c
@@ -0,0 +1,202 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "channelmap.h"
+
+pa_channel_map* pa_channel_map_init(pa_channel_map *m) {
+ unsigned c;
+ assert(m);
+
+ m->channels = 0;
+
+ for (c = 0; c < PA_CHANNELS_MAX; c++)
+ m->map[c] = PA_CHANNEL_POSITION_INVALID;
+
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) {
+ assert(m);
+
+ pa_channel_map_init(m);
+
+ m->channels = 1;
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) {
+ assert(m);
+
+ pa_channel_map_init(m);
+
+ m->channels = 2;
+ m->map[0] = PA_CHANNEL_POSITION_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_RIGHT;
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels) {
+ assert(m);
+ assert(channels > 0);
+ assert(channels <= PA_CHANNELS_MAX);
+
+ pa_channel_map_init(m);
+
+ m->channels = channels;
+
+ switch (channels) {
+ case 1:
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+
+ case 8:
+ m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ /* Fall through */
+
+ case 6:
+ m->map[5] = PA_CHANNEL_POSITION_LFE;
+ /* Fall through */
+
+ case 5:
+ m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ /* Fall through */
+
+ case 4:
+ m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+ m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+
+ case 2:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ return m;
+
+ default:
+ return NULL;
+ }
+}
+
+const char* pa_channel_position_to_string(pa_channel_position_t pos) {
+
+ const char *const table[] = {
+ [PA_CHANNEL_POSITION_MONO] = "mono",
+
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center",
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right",
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center",
+ [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left",
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right",
+
+ [PA_CHANNEL_POSITION_LFE] = "lfe",
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center",
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left",
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right",
+
+ [PA_CHANNEL_POSITION_AUX1] = "aux1",
+ [PA_CHANNEL_POSITION_AUX2] = "aux2",
+ [PA_CHANNEL_POSITION_AUX3] = "aux3",
+ [PA_CHANNEL_POSITION_AUX4] = "aux4",
+ [PA_CHANNEL_POSITION_AUX5] = "aux5",
+ [PA_CHANNEL_POSITION_AUX6] = "aux6",
+ [PA_CHANNEL_POSITION_AUX7] = "aux7",
+ [PA_CHANNEL_POSITION_AUX8] = "aux8",
+ [PA_CHANNEL_POSITION_AUX9] = "aux9",
+ [PA_CHANNEL_POSITION_AUX10] = "aux10",
+ [PA_CHANNEL_POSITION_AUX11] = "aux11",
+ [PA_CHANNEL_POSITION_AUX12] = "aux12"
+ };
+
+ if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX)
+ return NULL;
+
+ return table[pos];
+}
+
+int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) {
+ unsigned c;
+
+ assert(a);
+ assert(b);
+
+ if (a->channels != b->channels)
+ return 0;
+
+ for (c = 0; c < a->channels; c++)
+ if (a->map[c] != b->map[c])
+ return 0;
+
+ return 1;
+}
+
+char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {
+ unsigned channel;
+ int first = 1;
+ char *e;
+
+ assert(s);
+ assert(l > 0);
+ assert(map);
+
+ *(e = s) = 0;
+
+ for (channel = 0; channel < map->channels && l > 1; channel++) {
+ l -= snprintf(e, l, "%s%u:%s",
+ first ? "" : " ",
+ channel,
+ pa_channel_position_to_string(map->map[channel]));
+
+ e = strchr(e, 0);
+ first = 0;
+ }
+
+ return s;
+}
+
+int pa_channel_map_valid(const pa_channel_map *map) {
+ unsigned c;
+
+ assert(map);
+
+ if (map->channels <= 0 || map->channels > PA_CHANNELS_MAX)
+ return 0;
+
+ for (c = 0; c < map->channels; c++)
+ if (map->map[c] < 0 ||map->map[c] >= PA_CHANNEL_POSITION_MAX)
+ return 0;
+
+ return 1;
+}
diff --git a/src/polyp/channelmap.h b/src/polyp/channelmap.h
new file mode 100644
index 00000000..0b9f6e26
--- /dev/null
+++ b/src/polyp/channelmap.h
@@ -0,0 +1,98 @@
+#ifndef foochannelmaphfoo
+#define foochannelmaphfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/sample.h>
+#include <polyp/cdecl.h>
+
+/** \file
+ * Constants and routines for channel mapping handling */
+
+PA_C_DECL_BEGIN
+
+typedef enum {
+ PA_CHANNEL_POSITION_INVALID = -1,
+ PA_CHANNEL_POSITION_MONO = 0,
+
+ PA_CHANNEL_POSITION_LEFT,
+ PA_CHANNEL_POSITION_RIGHT,
+
+ PA_CHANNEL_POSITION_FRONT_CENTER,
+ PA_CHANNEL_POSITION_FRONT_LEFT = PA_CHANNEL_POSITION_LEFT,
+ PA_CHANNEL_POSITION_FRONT_RIGHT = PA_CHANNEL_POSITION_RIGHT,
+
+ PA_CHANNEL_POSITION_REAR_CENTER,
+ PA_CHANNEL_POSITION_REAR_LEFT,
+ PA_CHANNEL_POSITION_REAR_RIGHT,
+
+ PA_CHANNEL_POSITION_LFE,
+ PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE,
+
+ PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+
+ PA_CHANNEL_POSITION_SIDE_LEFT,
+ PA_CHANNEL_POSITION_SIDE_RIGHT,
+
+ PA_CHANNEL_POSITION_AUX0,
+ PA_CHANNEL_POSITION_AUX1,
+ PA_CHANNEL_POSITION_AUX2,
+ PA_CHANNEL_POSITION_AUX3,
+ PA_CHANNEL_POSITION_AUX4,
+ PA_CHANNEL_POSITION_AUX5,
+ PA_CHANNEL_POSITION_AUX6,
+ PA_CHANNEL_POSITION_AUX7,
+ PA_CHANNEL_POSITION_AUX8,
+ PA_CHANNEL_POSITION_AUX9,
+ PA_CHANNEL_POSITION_AUX10,
+ PA_CHANNEL_POSITION_AUX11,
+ PA_CHANNEL_POSITION_AUX12,
+ PA_CHANNEL_POSITION_AUX13,
+ PA_CHANNEL_POSITION_AUX14,
+ PA_CHANNEL_POSITION_AUX15,
+
+ PA_CHANNEL_POSITION_MAX
+} pa_channel_position_t;
+
+typedef struct pa_channel_map {
+ uint8_t channels;
+ pa_channel_position_t map[PA_CHANNELS_MAX];
+} pa_channel_map;
+
+pa_channel_map* pa_channel_map_init(pa_channel_map *m);
+pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m);
+pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m);
+pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels);
+
+const char* pa_channel_position_to_string(pa_channel_position_t pos);
+
+#define PA_CHANNEL_MAP_SNPRINT_MAX 64
+char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map);
+
+int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b);
+
+int pa_channel_map_valid(const pa_channel_map *map);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/client-conf-x11.c b/src/polyp/client-conf-x11.c
new file mode 100644
index 00000000..83d0bd2e
--- /dev/null
+++ b/src/polyp/client-conf-x11.c
@@ -0,0 +1,91 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-13071
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "client-conf-x11.h"
+#include <polypcore/x11prop.h>
+#include <polypcore/log.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/util.h>
+
+int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) {
+ Display *d = NULL;
+ int ret = -1;
+ char t[1024];
+
+ if (!dname && !getenv("DISPLAY"))
+ goto finish;
+
+ if (!(d = XOpenDisplay(dname))) {
+ pa_log(__FILE__": XOpenDisplay() failed\n");
+ goto finish;
+ }
+
+ if (pa_x11_get_prop(d, "POLYP_SERVER", t, sizeof(t))) {
+ pa_xfree(c->default_server);
+ c->default_server = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "POLYP_SINK", t, sizeof(t))) {
+ pa_xfree(c->default_sink);
+ c->default_sink = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "POLYP_SOURCE", t, sizeof(t))) {
+ pa_xfree(c->default_source);
+ c->default_source = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "POLYP_COOKIE", t, sizeof(t))) {
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ if (pa_parsehex(t, cookie, sizeof(cookie)) != sizeof(cookie)) {
+ pa_log(__FILE__": failed to parse cookie data\n");
+ goto finish;
+ }
+
+ assert(sizeof(cookie) == sizeof(c->cookie));
+ memcpy(c->cookie, cookie, sizeof(cookie));
+
+ c->cookie_valid = 1;
+
+ pa_xfree(c->cookie_file);
+ c->cookie_file = NULL;
+ }
+
+ ret = 0;
+
+finish:
+ if (d)
+ XCloseDisplay(d);
+
+ return ret;
+
+}
diff --git a/src/polyp/client-conf-x11.h b/src/polyp/client-conf-x11.h
new file mode 100644
index 00000000..80841171
--- /dev/null
+++ b/src/polyp/client-conf-x11.h
@@ -0,0 +1,31 @@
+#ifndef fooclientconfx11hfoo
+#define fooclientconfx11hfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/client-conf.h>
+
+/* Load client configuration data from the specified X11 display,
+ * overwriting the current settings in *c */
+int pa_client_conf_from_x11(pa_client_conf *c, const char *display);
+
+#endif
diff --git a/src/polyp/client-conf.c b/src/polyp/client-conf.c
new file mode 100644
index 00000000..2df201ce
--- /dev/null
+++ b/src/polyp/client-conf.c
@@ -0,0 +1,191 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/conf-parser.h>
+#include <polypcore/util.h>
+#include <polypcore/authkey.h>
+
+#include <polyp/client-conf.h>
+
+#ifndef DEFAULT_CONFIG_DIR
+# ifndef OS_IS_WIN32
+# define DEFAULT_CONFIG_DIR "/etc/polypaudio"
+# else
+# define DEFAULT_CONFIG_DIR "%POLYP_ROOT%"
+# endif
+#endif
+
+#ifndef OS_IS_WIN32
+# define PATH_SEP "/"
+#else
+# define PATH_SEP "\\"
+#endif
+
+#define DEFAULT_CLIENT_CONFIG_FILE DEFAULT_CONFIG_DIR PATH_SEP "client.conf"
+#define DEFAULT_CLIENT_CONFIG_FILE_USER ".polypaudio" PATH_SEP "client.conf"
+
+#define ENV_CLIENT_CONFIG_FILE "POLYP_CLIENTCONFIG"
+#define ENV_DEFAULT_SINK "POLYP_SINK"
+#define ENV_DEFAULT_SOURCE "POLYP_SOURCE"
+#define ENV_DEFAULT_SERVER "POLYP_SERVER"
+#define ENV_DAEMON_BINARY "POLYP_BINARY"
+#define ENV_COOKIE_FILE "POLYP_COOKIE"
+
+static const pa_client_conf default_conf = {
+ .daemon_binary = NULL,
+ .extra_arguments = NULL,
+ .default_sink = NULL,
+ .default_source = NULL,
+ .default_server = NULL,
+ .autospawn = 0,
+ .cookie_file = NULL,
+ .cookie_valid = 0
+};
+
+pa_client_conf *pa_client_conf_new(void) {
+ pa_client_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf));
+
+ c->daemon_binary = pa_xstrdup(POLYPAUDIO_BINARY);
+ c->extra_arguments = pa_xstrdup("--log-target=syslog --exit-idle-time=5");
+ c->cookie_file = pa_xstrdup(PA_NATIVE_COOKIE_FILE);
+
+ return c;
+}
+
+void pa_client_conf_free(pa_client_conf *c) {
+ assert(c);
+ pa_xfree(c->daemon_binary);
+ pa_xfree(c->extra_arguments);
+ pa_xfree(c->default_sink);
+ pa_xfree(c->default_source);
+ pa_xfree(c->default_server);
+ pa_xfree(c->cookie_file);
+ pa_xfree(c);
+}
+int pa_client_conf_load(pa_client_conf *c, const char *filename) {
+ FILE *f = NULL;
+ char *fn = NULL;
+ int r = -1;
+
+ /* Prepare the configuration parse table */
+ pa_config_item table[] = {
+ { "daemon-binary", pa_config_parse_string, NULL },
+ { "extra-arguments", pa_config_parse_string, NULL },
+ { "default-sink", pa_config_parse_string, NULL },
+ { "default-source", pa_config_parse_string, NULL },
+ { "default-server", pa_config_parse_string, NULL },
+ { "autospawn", pa_config_parse_bool, NULL },
+ { "cookie-file", pa_config_parse_string, NULL },
+ { NULL, NULL, NULL },
+ };
+
+ table[0].data = &c->daemon_binary;
+ table[1].data = &c->extra_arguments;
+ table[2].data = &c->default_sink;
+ table[3].data = &c->default_source;
+ table[4].data = &c->default_server;
+ table[5].data = &c->autospawn;
+ table[6].data = &c->cookie_file;
+
+ f = filename ?
+ fopen((fn = pa_xstrdup(filename)), "r") :
+ pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn);
+
+ if (!f && errno != EINTR) {
+ pa_log(__FILE__": WARNING: failed to open configuration file '%s': %s\n", filename, strerror(errno));
+ goto finish;
+ }
+
+ r = f ? pa_config_parse(fn, f, table, NULL) : 0;
+
+ if (!r)
+ r = pa_client_conf_load_cookie(c);
+
+
+finish:
+ pa_xfree(fn);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int pa_client_conf_env(pa_client_conf *c) {
+ char *e;
+
+ if ((e = getenv(ENV_DEFAULT_SINK))) {
+ pa_xfree(c->default_sink);
+ c->default_sink = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DEFAULT_SOURCE))) {
+ pa_xfree(c->default_source);
+ c->default_source = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DEFAULT_SERVER))) {
+ pa_xfree(c->default_server);
+ c->default_server = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DAEMON_BINARY))) {
+ pa_xfree(c->daemon_binary);
+ c->daemon_binary = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_COOKIE_FILE))) {
+ pa_xfree(c->cookie_file);
+ c->cookie_file = pa_xstrdup(e);
+
+ return pa_client_conf_load_cookie(c);
+ }
+
+ return 0;
+}
+
+int pa_client_conf_load_cookie(pa_client_conf* c) {
+ assert(c);
+
+ c->cookie_valid = 0;
+
+ if (!c->cookie_file)
+ return -1;
+
+ if (pa_authkey_load_auto(c->cookie_file, c->cookie, sizeof(c->cookie)) < 0)
+ return -1;
+
+ c->cookie_valid = 1;
+ return 0;
+}
+
diff --git a/src/polyp/client-conf.h b/src/polyp/client-conf.h
new file mode 100644
index 00000000..2d8a019f
--- /dev/null
+++ b/src/polyp/client-conf.h
@@ -0,0 +1,52 @@
+#ifndef fooclientconfhfoo
+#define fooclientconfhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include "../polypcore/native-common.h"
+
+/* A structure containing configuration data for polypaudio clients. */
+
+typedef struct pa_client_conf {
+ char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file;
+ int autospawn;
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+ int cookie_valid; /* non-zero, when cookie is valid */
+} pa_client_conf;
+
+/* Create a new configuration data object and reset it to defaults */
+pa_client_conf *pa_client_conf_new(void);
+void pa_client_conf_free(pa_client_conf *c);
+
+/* Load the configuration data from the speicified file, overwriting
+ * the current settings in *c. When the filename is NULL, the
+ * default client configuration file name is used. */
+int pa_client_conf_load(pa_client_conf *c, const char *filename);
+
+/* Load the configuration data from the environment of the current
+ process, overwriting the current settings in *c. */
+int pa_client_conf_env(pa_client_conf *c);
+
+/* Load cookie data from c->cookie_file into c->cookie */
+int pa_client_conf_load_cookie(pa_client_conf* c);
+
+#endif
diff --git a/src/polyp/glib-mainloop.c b/src/polyp/glib-mainloop.c
new file mode 100644
index 00000000..962eb574
--- /dev/null
+++ b/src/polyp/glib-mainloop.c
@@ -0,0 +1,538 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include <polyp/glib-mainloop.h>
+#include <polypcore/idxset.h>
+#include <polypcore/xmalloc.h>
+#include "glib.h"
+#include <polypcore/util.h>
+
+struct pa_io_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ GIOChannel *io_channel;
+ GSource *source;
+ GIOCondition io_condition;
+ int fd;
+ void (*callback) (pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_io_event *e, void *userdata);
+ pa_io_event *next, *prev;
+};
+
+struct pa_time_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ GSource *source;
+ struct timeval timeval;
+ void (*callback) (pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_time_event*e, void *userdata);
+ pa_time_event *next, *prev;
+};
+
+struct pa_defer_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ GSource *source;
+ void (*callback) (pa_mainloop_api*m, pa_defer_event *e, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_defer_event*e, void *userdata);
+ pa_defer_event *next, *prev;
+};
+
+struct pa_glib_mainloop {
+ GMainContext *glib_main_context;
+ pa_mainloop_api api;
+ GSource *cleanup_source;
+ pa_io_event *io_events, *dead_io_events;
+ pa_time_event *time_events, *dead_time_events;
+ pa_defer_event *defer_events, *dead_defer_events;
+};
+
+static void schedule_free_dead_events(pa_glib_mainloop *g);
+
+static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f);
+
+static pa_io_event* glib_io_new(pa_mainloop_api*m, int fd, pa_io_event_flags_t f, void (*callback) (pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata), void *userdata) {
+ pa_io_event *e;
+ pa_glib_mainloop *g;
+
+ assert(m && m->userdata && fd >= 0 && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_io_event));
+ e->mainloop = m->userdata;
+ e->dead = 0;
+ e->fd = fd;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ e->io_channel = g_io_channel_unix_new(e->fd);
+ assert(e->io_channel);
+ e->source = NULL;
+ e->io_condition = 0;
+
+ glib_io_enable(e, f);
+
+ e->next = g->io_events;
+ if (e->next) e->next->prev = e;
+ g->io_events = e;
+ e->prev = NULL;
+
+ return e;
+}
+
+/* The callback GLIB calls whenever an IO condition is met */
+static gboolean io_cb(GIOChannel *source, GIOCondition condition, gpointer data) {
+ pa_io_event *e = data;
+ pa_io_event_flags_t f;
+ assert(source && e && e->io_channel == source);
+
+ f = (condition & G_IO_IN ? PA_IO_EVENT_INPUT : 0) |
+ (condition & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (condition & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) |
+ (condition & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0);
+
+ e->callback(&e->mainloop->api, e, e->fd, f, e->userdata);
+ return TRUE;
+}
+
+static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) {
+ GIOCondition c;
+ assert(e && !e->dead);
+
+ c = (f & PA_IO_EVENT_INPUT ? G_IO_IN : 0) | (f & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0);
+
+ if (c == e->io_condition)
+ return;
+
+ if (e->source) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ }
+
+ e->source = g_io_create_watch(e->io_channel, c | G_IO_ERR | G_IO_HUP);
+ assert(e->source);
+
+ g_source_set_callback(e->source, (GSourceFunc) io_cb, e, NULL);
+ g_source_attach(e->source, e->mainloop->glib_main_context);
+ g_source_set_priority(e->source, G_PRIORITY_DEFAULT);
+
+ e->io_condition = c;
+}
+
+static void glib_io_free(pa_io_event*e) {
+ assert(e && !e->dead);
+
+ if (e->source) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ e->source = NULL;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->io_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_io_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_io_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_io_set_destroy(pa_io_event*e, void (*callback)(pa_mainloop_api*m, pa_io_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Time sources */
+
+static void glib_time_restart(pa_time_event*e, const struct timeval *tv);
+
+static pa_time_event* glib_time_new(pa_mainloop_api*m, const struct timeval *tv, void (*callback) (pa_mainloop_api*m, pa_time_event*e, const struct timeval *tv, void *userdata), void *userdata) {
+ pa_glib_mainloop *g;
+ pa_time_event *e;
+
+ assert(m && m->userdata && tv && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_time_event));
+ e->mainloop = g;
+ e->dead = 0;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+ e->source = NULL;
+
+ glib_time_restart(e, tv);
+
+ e->next = g->time_events;
+ if (e->next) e->next->prev = e;
+ g->time_events = e;
+ e->prev = NULL;
+
+ return e;
+}
+
+static guint msec_diff(const struct timeval *a, const struct timeval *b) {
+ guint r;
+ assert(a && b);
+
+ if (a->tv_sec < b->tv_sec)
+ return 0;
+
+ if (a->tv_sec == b->tv_sec && a->tv_sec <= b->tv_sec)
+ return 0;
+
+ r = (a->tv_sec-b->tv_sec)*1000;
+
+ if (a->tv_usec >= b->tv_usec)
+ r += (a->tv_usec - b->tv_usec) / 1000;
+ else
+ r -= (b->tv_usec - a->tv_usec) / 1000;
+
+ return r;
+}
+
+static gboolean time_cb(gpointer data) {
+ pa_time_event* e = data;
+ assert(e && e->mainloop && e->source);
+
+ g_source_unref(e->source);
+ e->source = NULL;
+
+ e->callback(&e->mainloop->api, e, &e->timeval, e->userdata);
+ return FALSE;
+}
+
+static void glib_time_restart(pa_time_event*e, const struct timeval *tv) {
+ struct timeval now;
+ assert(e && e->mainloop && !e->dead);
+
+ pa_gettimeofday(&now);
+ if (e->source) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ }
+
+ if (tv) {
+ e->timeval = *tv;
+ e->source = g_timeout_source_new(msec_diff(tv, &now));
+ assert(e->source);
+ g_source_set_callback(e->source, time_cb, e, NULL);
+ g_source_set_priority(e->source, G_PRIORITY_DEFAULT);
+ g_source_attach(e->source, e->mainloop->glib_main_context);
+ } else
+ e->source = NULL;
+ }
+
+static void glib_time_free(pa_time_event *e) {
+ assert(e && e->mainloop && !e->dead);
+
+ if (e->source) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ e->source = NULL;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->time_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_time_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_time_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_time_set_destroy(pa_time_event *e, void (*callback)(pa_mainloop_api*m, pa_time_event*e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Deferred sources */
+
+static void glib_defer_enable(pa_defer_event *e, int b);
+
+static pa_defer_event* glib_defer_new(pa_mainloop_api*m, void (*callback) (pa_mainloop_api*m, pa_defer_event *e, void *userdata), void *userdata) {
+ pa_defer_event *e;
+ pa_glib_mainloop *g;
+
+ assert(m && m->userdata && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_defer_event));
+ e->mainloop = g;
+ e->dead = 0;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+ e->source = NULL;
+
+ glib_defer_enable(e, 1);
+
+ e->next = g->defer_events;
+ if (e->next) e->next->prev = e;
+ g->defer_events = e;
+ e->prev = NULL;
+ return e;
+}
+
+static gboolean idle_cb(gpointer data) {
+ pa_defer_event* e = data;
+ assert(e && e->mainloop && e->source);
+
+ e->callback(&e->mainloop->api, e, e->userdata);
+ return TRUE;
+}
+
+static void glib_defer_enable(pa_defer_event *e, int b) {
+ assert(e && e->mainloop);
+
+ if (e->source && !b) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ e->source = NULL;
+ } else if (!e->source && b) {
+ e->source = g_idle_source_new();
+ assert(e->source);
+ g_source_set_callback(e->source, idle_cb, e, NULL);
+ g_source_attach(e->source, e->mainloop->glib_main_context);
+ g_source_set_priority(e->source, G_PRIORITY_HIGH);
+ }
+}
+
+static void glib_defer_free(pa_defer_event *e) {
+ assert(e && e->mainloop && !e->dead);
+
+ if (e->source) {
+ g_source_destroy(e->source);
+ g_source_unref(e->source);
+ e->source = NULL;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->defer_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_defer_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_defer_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_defer_set_destroy(pa_defer_event *e, void (*callback)(pa_mainloop_api *m, pa_defer_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* quit() */
+
+static void glib_quit(pa_mainloop_api*a, PA_GCC_UNUSED int retval) {
+ pa_glib_mainloop *g;
+ assert(a && a->userdata);
+ g = a->userdata;
+
+ /* NOOP */
+}
+
+static const pa_mainloop_api vtable = {
+ .userdata = NULL,
+
+ .io_new = glib_io_new,
+ .io_enable = glib_io_enable,
+ .io_free = glib_io_free,
+ .io_set_destroy= glib_io_set_destroy,
+
+ .time_new = glib_time_new,
+ .time_restart = glib_time_restart,
+ .time_free = glib_time_free,
+ .time_set_destroy = glib_time_set_destroy,
+
+ .defer_new = glib_defer_new,
+ .defer_enable = glib_defer_enable,
+ .defer_free = glib_defer_free,
+ .defer_set_destroy = glib_defer_set_destroy,
+
+ .quit = glib_quit,
+};
+
+pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) {
+ pa_glib_mainloop *g;
+
+ g = pa_xmalloc(sizeof(pa_glib_mainloop));
+ if (c) {
+ g->glib_main_context = c;
+ g_main_context_ref(c);
+ } else
+ g->glib_main_context = g_main_context_default();
+
+ g->api = vtable;
+ g->api.userdata = g;
+
+ g->io_events = g->dead_io_events = NULL;
+ g->time_events = g->dead_time_events = NULL;
+ g->defer_events = g->dead_defer_events = NULL;
+
+ g->cleanup_source = NULL;
+ return g;
+}
+
+static void free_io_events(pa_io_event *e) {
+ while (e) {
+ pa_io_event *r = e;
+ e = r->next;
+
+ if (r->source) {
+ g_source_destroy(r->source);
+ g_source_unref(r->source);
+ }
+
+ if (r->io_channel)
+ g_io_channel_unref(r->io_channel);
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+static void free_time_events(pa_time_event *e) {
+ while (e) {
+ pa_time_event *r = e;
+ e = r->next;
+
+ if (r->source) {
+ g_source_destroy(r->source);
+ g_source_unref(r->source);
+ }
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+static void free_defer_events(pa_defer_event *e) {
+ while (e) {
+ pa_defer_event *r = e;
+ e = r->next;
+
+ if (r->source) {
+ g_source_destroy(r->source);
+ g_source_unref(r->source);
+ }
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+void pa_glib_mainloop_free(pa_glib_mainloop* g) {
+ assert(g);
+
+ free_io_events(g->io_events);
+ free_io_events(g->dead_io_events);
+ free_defer_events(g->defer_events);
+ free_defer_events(g->dead_defer_events);
+ free_time_events(g->time_events);
+ free_time_events(g->dead_time_events);
+
+ if (g->cleanup_source) {
+ g_source_destroy(g->cleanup_source);
+ g_source_unref(g->cleanup_source);
+ }
+
+ g_main_context_unref(g->glib_main_context);
+ pa_xfree(g);
+}
+
+pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) {
+ assert(g);
+ return &g->api;
+}
+
+static gboolean free_dead_events(gpointer p) {
+ pa_glib_mainloop *g = p;
+ assert(g);
+
+ free_io_events(g->dead_io_events);
+ free_defer_events(g->dead_defer_events);
+ free_time_events(g->dead_time_events);
+
+ g->dead_io_events = NULL;
+ g->dead_defer_events = NULL;
+ g->dead_time_events = NULL;
+
+ g_source_destroy(g->cleanup_source);
+ g_source_unref(g->cleanup_source);
+ g->cleanup_source = NULL;
+
+ return FALSE;
+}
+
+static void schedule_free_dead_events(pa_glib_mainloop *g) {
+ assert(g && g->glib_main_context);
+
+ if (g->cleanup_source)
+ return;
+
+ g->cleanup_source = g_idle_source_new();
+ assert(g->cleanup_source);
+ g_source_set_callback(g->cleanup_source, free_dead_events, g, NULL);
+ g_source_attach(g->cleanup_source, g->glib_main_context);
+}
diff --git a/src/polyp/glib-mainloop.h b/src/polyp/glib-mainloop.h
new file mode 100644
index 00000000..b4815ed9
--- /dev/null
+++ b/src/polyp/glib-mainloop.h
@@ -0,0 +1,55 @@
+#ifndef fooglibmainloophfoo
+#define fooglibmainloophfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <glib.h>
+
+#include <polyp/mainloop-api.h>
+#include <polyp/cdecl.h>
+
+/** \file
+ * GLIB main loop support */
+
+PA_C_DECL_BEGIN
+
+/** \pa_glib_mainloop
+ * An opaque GLIB main loop object */
+typedef struct pa_glib_mainloop pa_glib_mainloop;
+
+/** Create a new GLIB main loop object for the specified GLIB main loop context. If c is NULL the default context is used. */
+#if GLIB_MAJOR_VERSION >= 2
+pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c);
+#else
+pa_glib_mainloop *pa_glib_mainloop_new(void);
+#endif
+
+
+/** Free the GLIB main loop object */
+void pa_glib_mainloop_free(pa_glib_mainloop* g);
+
+/** Return the abstract main loop API vtable for the GLIB main loop object */
+pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/glib12-mainloop.c b/src/polyp/glib12-mainloop.c
new file mode 100644
index 00000000..80a02b1c
--- /dev/null
+++ b/src/polyp/glib12-mainloop.c
@@ -0,0 +1,500 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include <polyp/glib-mainloop.h>
+#include <polypcore/idxset.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/util.h>
+
+/* A mainloop implementation based on GLIB 1.2 */
+
+struct pa_io_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ GIOChannel *io_channel;
+ guint source;
+ GIOCondition io_condition;
+ int fd;
+ void (*callback) (pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_io_event*e, void *userdata);
+ pa_io_event *next, *prev;
+};
+
+struct pa_time_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ guint source;
+ struct timeval timeval;
+ void (*callback) (pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_time_event*e, void *userdata);
+ pa_time_event *next, *prev;
+};
+
+struct pa_defer_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+ guint source;
+ void (*callback) (pa_mainloop_api*m, pa_defer_event *e, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api *m, pa_defer_event*e, void *userdata);
+ pa_defer_event *next, *prev;
+};
+
+struct pa_glib_mainloop {
+ pa_mainloop_api api;
+ guint cleanup_source;
+ pa_io_event *io_events, *dead_io_events;
+ pa_time_event *time_events, *dead_time_events;
+ pa_defer_event *defer_events, *dead_defer_events;
+};
+
+static void schedule_free_dead_events(pa_glib_mainloop *g);
+
+static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f);
+
+static pa_io_event* glib_io_new(pa_mainloop_api*m, int fd, pa_io_event_flags_t f, void (*callback) (pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata), void *userdata) {
+ pa_io_event *e;
+ pa_glib_mainloop *g;
+
+ assert(m && m->userdata && fd >= 0 && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_io_event));
+ e->mainloop = m->userdata;
+ e->dead = 0;
+ e->fd = fd;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ e->io_channel = g_io_channel_unix_new(e->fd);
+ assert(e->io_channel);
+ e->source = (guint) -1;
+ e->io_condition = 0;
+
+ glib_io_enable(e, f);
+
+ e->next = g->io_events;
+ if (e->next) e->next->prev = e;
+ g->io_events = e;
+ e->prev = NULL;
+
+ return e;
+}
+
+static gboolean io_cb(GIOChannel *source, GIOCondition condition, gpointer data) {
+ pa_io_event *e = data;
+ pa_io_event_flags_t f;
+ assert(source && e && e->io_channel == source);
+
+ f = (condition & G_IO_IN ? PA_IO_EVENT_INPUT : 0) |
+ (condition & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (condition & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) |
+ (condition & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0);
+
+ e->callback(&e->mainloop->api, e, e->fd, f, e->userdata);
+ return TRUE;
+}
+
+static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) {
+ GIOCondition c;
+ assert(e && !e->dead);
+
+ c = (f & PA_IO_EVENT_INPUT ? G_IO_IN : 0) | (f & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0);
+
+ if (c == e->io_condition)
+ return;
+
+ if (e->source != (guint) -1)
+ g_source_remove(e->source);
+
+ e->source = g_io_add_watch_full(e->io_channel, G_PRIORITY_DEFAULT, c | G_IO_ERR | G_IO_HUP, io_cb, e, NULL);
+ assert(e->source != (guint) -1);
+ e->io_condition = c;
+}
+
+static void glib_io_free(pa_io_event*e) {
+ assert(e && !e->dead);
+
+ if (e->source != (guint) -1) {
+ g_source_remove(e->source);
+ e->source = (guint) -1;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->io_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_io_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_io_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_io_set_destroy(pa_io_event*e, void (*callback)(pa_mainloop_api*m, pa_io_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Time sources */
+
+static void glib_time_restart(pa_time_event*e, const struct timeval *tv);
+
+static pa_time_event* glib_time_new(pa_mainloop_api*m, const struct timeval *tv, void (*callback) (pa_mainloop_api*m, pa_time_event*e, const struct timeval *tv, void *userdata), void *userdata) {
+ pa_glib_mainloop *g;
+ pa_time_event *e;
+
+ assert(m && m->userdata && tv && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_time_event));
+ e->mainloop = g;
+ e->dead = 0;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+ e->source = (guint) -1;
+
+ glib_time_restart(e, tv);
+
+ e->next = g->time_events;
+ if (e->next) e->next->prev = e;
+ g->time_events = e;
+ e->prev = NULL;
+
+ return e;
+}
+
+static guint msec_diff(const struct timeval *a, const struct timeval *b) {
+ guint r;
+ assert(a && b);
+
+ if (a->tv_sec < b->tv_sec)
+ return 0;
+
+ if (a->tv_sec == b->tv_sec && a->tv_sec <= b->tv_sec)
+ return 0;
+
+ r = (a->tv_sec-b->tv_sec)*1000;
+
+ if (a->tv_usec >= b->tv_usec)
+ r += (a->tv_usec - b->tv_usec) / 1000;
+ else
+ r -= (b->tv_usec - a->tv_usec) / 1000;
+
+ return r;
+}
+
+static gboolean time_cb(gpointer data) {
+ pa_time_event* e = data;
+ assert(e && e->mainloop && e->source != (guint) -1);
+
+ g_source_remove(e->source);
+ e->source = (guint) -1;
+
+ e->callback(&e->mainloop->api, e, &e->timeval, e->userdata);
+ return FALSE;
+}
+
+static void glib_time_restart(pa_time_event*e, const struct timeval *tv) {
+ struct timeval now;
+ assert(e && e->mainloop && !e->dead);
+
+ pa_gettimeofday(&now);
+ if (e->source != (guint) -1)
+ g_source_remove(e->source);
+
+ if (tv) {
+ e->timeval = *tv;
+ e->source = g_timeout_add_full(G_PRIORITY_DEFAULT, msec_diff(tv, &now), time_cb, e, NULL);
+ assert(e->source != (guint) -1);
+ } else
+ e->source = (guint) -1;
+ }
+
+static void glib_time_free(pa_time_event *e) {
+ assert(e && e->mainloop && !e->dead);
+
+ if (e->source != (guint) -1) {
+ g_source_remove(e->source);
+ e->source = (guint) -1;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->time_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_time_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_time_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_time_set_destroy(pa_time_event *e, void (*callback)(pa_mainloop_api*m, pa_time_event*e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Deferred sources */
+
+static void glib_defer_enable(pa_defer_event *e, int b);
+
+static pa_defer_event* glib_defer_new(pa_mainloop_api*m, void (*callback) (pa_mainloop_api*m, pa_defer_event *e, void *userdata), void *userdata) {
+ pa_defer_event *e;
+ pa_glib_mainloop *g;
+
+ assert(m && m->userdata && callback);
+ g = m->userdata;
+
+ e = pa_xmalloc(sizeof(pa_defer_event));
+ e->mainloop = g;
+ e->dead = 0;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+ e->source = (guint) -1;
+
+ glib_defer_enable(e, 1);
+
+ e->next = g->defer_events;
+ if (e->next) e->next->prev = e;
+ g->defer_events = e;
+ e->prev = NULL;
+ return e;
+}
+
+static gboolean idle_cb(gpointer data) {
+ pa_defer_event* e = data;
+ assert(e && e->mainloop && e->source != (guint) -1);
+
+ e->callback(&e->mainloop->api, e, e->userdata);
+ return TRUE;
+}
+
+static void glib_defer_enable(pa_defer_event *e, int b) {
+ assert(e && e->mainloop);
+
+ if (e->source != (guint) -1 && !b) {
+ g_source_remove(e->source);
+ e->source = (guint) -1;
+ } else if (e->source == (guint) -1 && b) {
+ e->source = g_idle_add_full(G_PRIORITY_HIGH, idle_cb, e, NULL);
+ assert(e->source != (guint) -1);
+ }
+}
+
+static void glib_defer_free(pa_defer_event *e) {
+ assert(e && e->mainloop && !e->dead);
+
+ if (e->source != (guint) -1) {
+ g_source_remove(e->source);
+ e->source = (guint) -1;
+ }
+
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ e->mainloop->defer_events = e->next;
+
+ if (e->next)
+ e->next->prev = e->prev;
+
+ if ((e->next = e->mainloop->dead_defer_events))
+ e->next->prev = e;
+
+ e->mainloop->dead_defer_events = e;
+ e->prev = NULL;
+
+ e->dead = 1;
+ schedule_free_dead_events(e->mainloop);
+}
+
+static void glib_defer_set_destroy(pa_defer_event *e, void (*callback)(pa_mainloop_api *m, pa_defer_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* quit() */
+
+static void glib_quit(pa_mainloop_api*a, PA_GCC_UNUSED int retval) {
+ pa_glib_mainloop *g;
+ assert(a && a->userdata);
+ g = a->userdata;
+
+ /* NOOP */
+}
+
+static const pa_mainloop_api vtable = {
+ .userdata = NULL,
+
+ .io_new = glib_io_new,
+ .io_enable = glib_io_enable,
+ .io_free = glib_io_free,
+ .io_set_destroy= glib_io_set_destroy,
+
+ .time_new = glib_time_new,
+ .time_restart = glib_time_restart,
+ .time_free = glib_time_free,
+ .time_set_destroy = glib_time_set_destroy,
+
+ .defer_new = glib_defer_new,
+ .defer_enable = glib_defer_enable,
+ .defer_free = glib_defer_free,
+ .defer_set_destroy = glib_defer_set_destroy,
+
+ .quit = glib_quit,
+};
+
+pa_glib_mainloop *pa_glib_mainloop_new(void) {
+ pa_glib_mainloop *g;
+
+ g = pa_xmalloc(sizeof(pa_glib_mainloop));
+
+ g->api = vtable;
+ g->api.userdata = g;
+
+ g->io_events = g->dead_io_events = NULL;
+ g->time_events = g->dead_time_events = NULL;
+ g->defer_events = g->dead_defer_events = NULL;
+
+ g->cleanup_source = (guint) -1;
+ return g;
+}
+
+static void free_io_events(pa_io_event *e) {
+ while (e) {
+ pa_io_event *r = e;
+ e = r->next;
+
+ if (r->source != (guint) -1)
+ g_source_remove(r->source);
+
+ if (r->io_channel)
+ g_io_channel_unref(r->io_channel);
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+static void free_time_events(pa_time_event *e) {
+ while (e) {
+ pa_time_event *r = e;
+ e = r->next;
+
+ if (r->source != (guint) -1)
+ g_source_remove(r->source);
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+static void free_defer_events(pa_defer_event *e) {
+ while (e) {
+ pa_defer_event *r = e;
+ e = r->next;
+
+ if (r->source != (guint) -1)
+ g_source_remove(r->source);
+
+ if (r->destroy_callback)
+ r->destroy_callback(&r->mainloop->api, r, r->userdata);
+
+ pa_xfree(r);
+ }
+}
+
+void pa_glib_mainloop_free(pa_glib_mainloop* g) {
+ assert(g);
+
+ free_io_events(g->io_events);
+ free_io_events(g->dead_io_events);
+ free_defer_events(g->defer_events);
+ free_defer_events(g->dead_defer_events);
+ free_time_events(g->time_events);
+ free_time_events(g->dead_time_events);
+
+ if (g->cleanup_source != (guint) -1)
+ g_source_remove(g->cleanup_source);
+
+ pa_xfree(g);
+}
+
+pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) {
+ assert(g);
+ return &g->api;
+}
+
+static gboolean free_dead_events(gpointer p) {
+ pa_glib_mainloop *g = p;
+ assert(g);
+
+ free_io_events(g->dead_io_events);
+ free_defer_events(g->dead_defer_events);
+ free_time_events(g->dead_time_events);
+
+ g->dead_io_events = NULL;
+ g->dead_defer_events = NULL;
+ g->dead_time_events = NULL;
+
+ g_source_remove(g->cleanup_source);
+ g->cleanup_source = (guint) -1;
+
+ return FALSE;
+}
+
+static void schedule_free_dead_events(pa_glib_mainloop *g) {
+ assert(g);
+
+ if (g->cleanup_source != (guint) -1)
+ return;
+
+ g->cleanup_source = g_idle_add_full(G_PRIORITY_HIGH, free_dead_events, g, NULL);
+}
diff --git a/src/polyp/mainloop-api.c b/src/polyp/mainloop-api.c
new file mode 100644
index 00000000..a3eaee9c
--- /dev/null
+++ b/src/polyp/mainloop-api.c
@@ -0,0 +1,68 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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.
+
+ polypaudio 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 polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "mainloop-api.h"
+#include <polypcore/xmalloc.h>
+#include <polypcore/gccmacro.h>
+
+struct once_info {
+ void (*callback)(pa_mainloop_api*m, void *userdata);
+ void *userdata;
+};
+
+static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+ struct once_info *i = userdata;
+ assert(m && i && i->callback);
+
+ i->callback(m, i->userdata);
+
+ assert(m->defer_free);
+ m->defer_free(e);
+}
+
+static void free_callback(pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event *e, void *userdata) {
+ struct once_info *i = userdata;
+ assert(m && i);
+ pa_xfree(i);
+}
+
+void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *m, void *userdata), void *userdata) {
+ struct once_info *i;
+ pa_defer_event *e;
+ assert(m && callback);
+
+ i = pa_xnew(struct once_info, 1);
+ i->callback = callback;
+ i->userdata = userdata;
+
+ assert(m->defer_new);
+ e = m->defer_new(m, once_callback, i);
+ assert(e);
+ m->defer_set_destroy(e, free_callback);
+}
+
diff --git a/src/polyp/mainloop-api.h b/src/polyp/mainloop-api.h
new file mode 100644
index 00000000..91ee4111
--- /dev/null
+++ b/src/polyp/mainloop-api.h
@@ -0,0 +1,120 @@
+#ifndef foomainloopapihfoo
+#define foomainloopapihfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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.
+
+ polypaudio 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 polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/time.h>
+#include <time.h>
+
+#include <polyp/cdecl.h>
+
+/** \file
+ *
+ * Main loop abstraction layer. Both the polypaudio core and the
+ * polypaudio client library use a main loop abstraction layer. Due to
+ * this it is possible to embed polypaudio into other
+ * applications easily. Two main loop implemenations are
+ * currently available:
+ * \li A minimal implementation based on the C library's poll() function (See \ref mainloop.h)
+ * \li A wrapper around the GLIB main loop. Use this to embed polypaudio into your GLIB/GTK+/GNOME programs (See \ref glib-mainloop.h)
+ *
+ * The structure pa_mainloop_api is used as vtable for the main loop abstraction.
+ *
+ * This mainloop abstraction layer has no direct support for UNIX signals. Generic, mainloop implementation agnostic support is available throught \ref mainloop-signal.h.
+ * */
+
+PA_C_DECL_BEGIN
+
+/** A bitmask for IO events */
+typedef enum pa_io_event_flags {
+ PA_IO_EVENT_NULL = 0, /**< No event */
+ PA_IO_EVENT_INPUT = 1, /**< Input event */
+ PA_IO_EVENT_OUTPUT = 2, /**< Output event */
+ PA_IO_EVENT_HANGUP = 4, /**< Hangup event */
+ PA_IO_EVENT_ERROR = 8 /**< Error event */
+} pa_io_event_flags_t;
+
+/** \pa_io_event
+ * An opaque IO event source object */
+typedef struct pa_io_event pa_io_event;
+
+/** \pa_defer_event
+ * An opaque deferred event source object. Events of this type are triggered once in every main loop iteration */
+typedef struct pa_defer_event pa_defer_event;
+
+/** \pa_time_event
+ * An opaque timer event source object */
+typedef struct pa_time_event pa_time_event;
+
+/** An abstract mainloop API vtable */
+typedef struct pa_mainloop_api pa_mainloop_api;
+
+struct pa_mainloop_api {
+ /** A pointer to some private, arbitrary data of the main loop implementation */
+ void *userdata;
+
+ /** Create a new IO event source object */
+ pa_io_event* (*io_new)(pa_mainloop_api*a, int fd, pa_io_event_flags_t events, void (*callback) (pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata), void *userdata);
+
+ /** Enable or disable IO events on this object */
+ void (*io_enable)(pa_io_event* e, pa_io_event_flags_t events);
+
+ /** Free a IO event source object */
+ void (*io_free)(pa_io_event* e);
+
+ /** Set a function that is called when the IO event source is destroyed. Use this to free the userdata argument if required */
+ void (*io_set_destroy)(pa_io_event *e, void (*callback) (pa_mainloop_api*a, pa_io_event *e, void *userdata));
+
+ /** Create a new timer event source object for the specified Unix time */
+ pa_time_event* (*time_new)(pa_mainloop_api*a, const struct timeval *tv, void (*callback) (pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata), void *userdata);
+
+ /** Restart a running or expired timer event source with a new Unix time */
+ void (*time_restart)(pa_time_event* e, const struct timeval *tv);
+
+ /** Free a deferred timer event source object */
+ void (*time_free)(pa_time_event* e);
+
+ /** Set a function that is called when the timer event source is destroyed. Use this to free the userdata argument if required */
+ void (*time_set_destroy)(pa_time_event *e, void (*callback) (pa_mainloop_api*a, pa_time_event *e, void *userdata));
+
+ /** Create a new deferred event source object */
+ pa_defer_event* (*defer_new)(pa_mainloop_api*a, void (*callback) (pa_mainloop_api*a, pa_defer_event* e, void *userdata), void *userdata);
+
+ /** Enable or disable a deferred event source temporarily */
+ void (*defer_enable)(pa_defer_event* e, int b);
+
+ /** Free a deferred event source object */
+ void (*defer_free)(pa_defer_event* e);
+
+ /** Set a function that is called when the deferred event source is destroyed. Use this to free the userdata argument if required */
+ void (*defer_set_destroy)(pa_defer_event *e, void (*callback) (pa_mainloop_api*a, pa_defer_event *e, void *userdata));
+
+ /** Exit the main loop and return the specfied retval*/
+ void (*quit)(pa_mainloop_api*a, int retval);
+};
+
+/** Run the specified callback function once from the main loop using an anonymous defer event. */
+void pa_mainloop_api_once(pa_mainloop_api*m, void (*callback)(pa_mainloop_api*m, void *userdata), void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/mainloop-signal.c b/src/polyp/mainloop-signal.c
new file mode 100644
index 00000000..a03c9159
--- /dev/null
+++ b/src/polyp/mainloop-signal.c
@@ -0,0 +1,267 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include <polyp/mainloop-signal.h>
+#include <polypcore/util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/gccmacro.h>
+
+struct pa_signal_event {
+ int sig;
+#ifdef HAVE_SIGACTION
+ struct sigaction saved_sigaction;
+#else
+ void (*saved_handler)(int sig);
+#endif
+ void (*callback) (pa_mainloop_api*a, pa_signal_event *e, int sig, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api*a, pa_signal_event*e, void *userdata);
+ pa_signal_event *previous, *next;
+};
+
+static pa_mainloop_api *api = NULL;
+static int signal_pipe[2] = { -1, -1 };
+static pa_io_event* io_event = NULL;
+static pa_defer_event *defer_event = NULL;
+static pa_signal_event *signals = NULL;
+
+#ifdef OS_IS_WIN32
+static unsigned int waiting_signals = 0;
+static CRITICAL_SECTION crit;
+#endif
+
+static void signal_handler(int sig) {
+#ifndef HAVE_SIGACTION
+ signal(sig, signal_handler);
+#endif
+ write(signal_pipe[1], &sig, sizeof(sig));
+
+#ifdef OS_IS_WIN32
+ EnterCriticalSection(&crit);
+ waiting_signals++;
+ LeaveCriticalSection(&crit);
+#endif
+}
+
+static void dispatch(pa_mainloop_api*a, int sig) {
+ pa_signal_event*s;
+
+ for (s = signals; s; s = s->next)
+ if (s->sig == sig) {
+ assert(s->callback);
+ s->callback(a, s, sig, s->userdata);
+ break;
+ }
+}
+
+static void defer(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event*e, PA_GCC_UNUSED void *userdata) {
+ ssize_t r;
+ int sig;
+ unsigned int sigs;
+
+#ifdef OS_IS_WIN32
+ EnterCriticalSection(&crit);
+ sigs = waiting_signals;
+ waiting_signals = 0;
+ LeaveCriticalSection(&crit);
+#endif
+
+ while (sigs) {
+ if ((r = read(signal_pipe[0], &sig, sizeof(sig))) < 0) {
+ pa_log(__FILE__": read(): %s\n", strerror(errno));
+ return;
+ }
+
+ if (r != sizeof(sig)) {
+ pa_log(__FILE__": short read()\n");
+ return;
+ }
+
+ dispatch(a, sig);
+
+ sigs--;
+ }
+}
+
+static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags_t f, PA_GCC_UNUSED void *userdata) {
+ ssize_t r;
+ int sig;
+ assert(a && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == signal_pipe[0]);
+
+
+ if ((r = read(signal_pipe[0], &sig, sizeof(sig))) < 0) {
+ if (errno == EAGAIN)
+ return;
+
+ pa_log(__FILE__": read(): %s\n", strerror(errno));
+ return;
+ }
+
+ if (r != sizeof(sig)) {
+ pa_log(__FILE__": short read()\n");
+ return;
+ }
+
+ dispatch(a, sig);
+}
+
+int pa_signal_init(pa_mainloop_api *a) {
+ assert(!api && a && signal_pipe[0] == -1 && signal_pipe[1] == -1 && !io_event && !defer_event);
+
+#ifdef OS_IS_WIN32
+ if (_pipe(signal_pipe, 200, _O_BINARY) < 0) {
+#else
+ if (pipe(signal_pipe) < 0) {
+#endif
+ pa_log(__FILE__": pipe() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ pa_make_nonblock_fd(signal_pipe[0]);
+ pa_make_nonblock_fd(signal_pipe[1]);
+ pa_fd_set_cloexec(signal_pipe[0], 1);
+ pa_fd_set_cloexec(signal_pipe[1], 1);
+
+ api = a;
+
+#ifndef OS_IS_WIN32
+ io_event = api->io_new(api, signal_pipe[0], PA_IO_EVENT_INPUT, callback, NULL);
+ assert(io_event);
+#else
+ defer_event = api->defer_new(api, defer, NULL);
+ assert(defer_event);
+
+ InitializeCriticalSection(&crit);
+#endif
+
+ return 0;
+}
+
+void pa_signal_done(void) {
+ assert(api && signal_pipe[0] >= 0 && signal_pipe[1] >= 0 && (io_event || defer_event));
+
+ while (signals)
+ pa_signal_free(signals);
+
+
+#ifndef OS_IS_WIN32
+ api->io_free(io_event);
+ io_event = NULL;
+#else
+ api->defer_free(defer_event);
+ defer_event = NULL;
+
+ DeleteCriticalSection(&crit);
+#endif
+
+ close(signal_pipe[0]);
+ close(signal_pipe[1]);
+ signal_pipe[0] = signal_pipe[1] = -1;
+
+ api = NULL;
+}
+
+pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata) {
+ pa_signal_event *e = NULL;
+
+#ifdef HAVE_SIGACTION
+ struct sigaction sa;
+#endif
+
+ assert(sig > 0 && _callback);
+
+ for (e = signals; e; e = e->next)
+ if (e->sig == sig)
+ goto fail;
+
+ e = pa_xmalloc(sizeof(pa_signal_event));
+ e->sig = sig;
+ e->callback = _callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+#ifdef HAVE_SIGACTION
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(sig, &sa, &e->saved_sigaction) < 0)
+#else
+ if ((e->saved_handler = signal(sig, signal_handler)) == SIG_ERR)
+#endif
+ goto fail;
+
+ e->previous = NULL;
+ e->next = signals;
+ signals = e;
+
+ return e;
+fail:
+ if (e)
+ pa_xfree(e);
+ return NULL;
+}
+
+void pa_signal_free(pa_signal_event *e) {
+ assert(e);
+
+ if (e->next)
+ e->next->previous = e->previous;
+ if (e->previous)
+ e->previous->next = e->next;
+ else
+ signals = e->next;
+
+#ifdef HAVE_SIGACTION
+ sigaction(e->sig, &e->saved_sigaction, NULL);
+#else
+ signal(e->sig, e->saved_handler);
+#endif
+
+ if (e->destroy_callback)
+ e->destroy_callback(api, e, e->userdata);
+
+ pa_xfree(e);
+}
+
+void pa_signal_set_destroy(pa_signal_event *e, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = _callback;
+}
diff --git a/src/polyp/mainloop-signal.h b/src/polyp/mainloop-signal.h
new file mode 100644
index 00000000..76065b22
--- /dev/null
+++ b/src/polyp/mainloop-signal.h
@@ -0,0 +1,60 @@
+#ifndef foomainloopsignalhfoo
+#define foomainloopsignalhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/mainloop-api.h>
+#include <polyp/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+/** \file
+ * UNIX signal support for main loops. In contrast to other
+ * main loop event sources such as timer and IO events, UNIX signal
+ * support requires modification of the global process
+ * environment. Due to this the generic main loop abstraction layer as
+ * defined in \ref mainloop-api.h doesn't have direct support for UNIX
+ * signals. However, you may hook signal support into an abstract main loop via the routines defined herein.
+ */
+
+/** Initialize the UNIX signal subsystem and bind it to the specified main loop */
+int pa_signal_init(pa_mainloop_api *api);
+
+/** Cleanup the signal subsystem */
+void pa_signal_done(void);
+
+/** \pa_signal_event
+ * An opaque UNIX signal event source object */
+typedef struct pa_signal_event pa_signal_event;
+
+/** Create a new UNIX signal event source object */
+pa_signal_event* pa_signal_new(int sig, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata);
+
+/** Free a UNIX signal event source object */
+void pa_signal_free(pa_signal_event *e);
+
+/** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */
+void pa_signal_set_destroy(pa_signal_event *e, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata));
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/mainloop.c b/src/polyp/mainloop.c
new file mode 100644
index 00000000..3fa9245c
--- /dev/null
+++ b/src/polyp/mainloop.c
@@ -0,0 +1,812 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include "poll.h"
+#endif
+
+#include <polypcore/winsock.h>
+
+#include "mainloop.h"
+#include <polypcore/util.h>
+#include <polypcore/idxset.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+struct pa_io_event {
+ pa_mainloop *mainloop;
+ int dead;
+ int fd;
+ pa_io_event_flags_t events;
+ void (*callback) (pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata);
+ struct pollfd *pollfd;
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api*a, pa_io_event *e, void *userdata);
+};
+
+struct pa_time_event {
+ pa_mainloop *mainloop;
+ int dead;
+ int enabled;
+ struct timeval timeval;
+ void (*callback)(pa_mainloop_api*a, pa_time_event *e, const struct timeval*tv, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api*a, pa_time_event *e, void *userdata);
+};
+
+struct pa_defer_event {
+ pa_mainloop *mainloop;
+ int dead;
+ int enabled;
+ void (*callback)(pa_mainloop_api*a, pa_defer_event*e, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api*a, pa_defer_event *e, void *userdata);
+};
+
+struct pa_mainloop {
+ pa_idxset *io_events, *time_events, *defer_events;
+ int io_events_scan_dead, defer_events_scan_dead, time_events_scan_dead;
+
+ struct pollfd *pollfds;
+ unsigned max_pollfds, n_pollfds;
+ int rebuild_pollfds;
+
+ int prepared_timeout;
+
+ int quit, retval;
+ pa_mainloop_api api;
+
+ int deferred_pending;
+
+ int wakeup_pipe[2];
+
+ enum {
+ STATE_PASSIVE,
+ STATE_PREPARED,
+ STATE_POLLING,
+ STATE_POLLED,
+ STATE_QUIT
+ } state;
+};
+
+/* IO events */
+static pa_io_event* mainloop_io_new(
+ pa_mainloop_api*a,
+ int fd,
+ pa_io_event_flags_t events,
+ void (*callback) (pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata),
+ void *userdata) {
+
+ pa_mainloop *m;
+ pa_io_event *e;
+
+ assert(a && a->userdata && fd >= 0 && callback);
+ m = a->userdata;
+ assert(a == &m->api);
+
+ pa_mainloop_wakeup(m);
+
+ e = pa_xmalloc(sizeof(pa_io_event));
+ e->mainloop = m;
+ e->dead = 0;
+
+ e->fd = fd;
+ e->events = events;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+ e->pollfd = NULL;
+
+#ifdef OS_IS_WIN32
+ {
+ fd_set xset;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ FD_ZERO (&xset);
+ FD_SET (fd, &xset);
+
+ if ((select((SELECT_TYPE_ARG1) fd, NULL, NULL, SELECT_TYPE_ARG234 &xset,
+ SELECT_TYPE_ARG5 &tv) == -1) &&
+ (WSAGetLastError() == WSAENOTSOCK)) {
+ pa_log_warn(__FILE__": WARNING: cannot monitor non-socket file descriptors.\n");
+ e->dead = 1;
+ }
+ }
+#endif
+
+ pa_idxset_put(m->io_events, e, NULL);
+ m->rebuild_pollfds = 1;
+ return e;
+}
+
+static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) {
+ assert(e && e->mainloop);
+
+ pa_mainloop_wakeup(e->mainloop);
+
+ e->events = events;
+ e->mainloop->rebuild_pollfds = 1;
+}
+
+static void mainloop_io_free(pa_io_event *e) {
+ assert(e && e->mainloop);
+
+ pa_mainloop_wakeup(e->mainloop);
+
+ e->dead = e->mainloop->io_events_scan_dead = e->mainloop->rebuild_pollfds = 1;
+}
+
+static void mainloop_io_set_destroy(pa_io_event *e, void (*callback)(pa_mainloop_api*a, pa_io_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Defer events */
+static pa_defer_event* mainloop_defer_new(pa_mainloop_api*a, void (*callback) (pa_mainloop_api*a, pa_defer_event *e, void *userdata), void *userdata) {
+ pa_mainloop *m;
+ pa_defer_event *e;
+
+ assert(a && a->userdata && callback);
+ m = a->userdata;
+ assert(a == &m->api);
+
+ e = pa_xmalloc(sizeof(pa_defer_event));
+ e->mainloop = m;
+ e->dead = 0;
+
+ e->enabled = 1;
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ pa_idxset_put(m->defer_events, e, NULL);
+
+ m->deferred_pending++;
+ return e;
+}
+
+static void mainloop_defer_enable(pa_defer_event *e, int b) {
+ assert(e);
+
+ if (e->enabled && !b) {
+ assert(e->mainloop->deferred_pending > 0);
+ e->mainloop->deferred_pending--;
+ } else if (!e->enabled && b)
+ e->mainloop->deferred_pending++;
+
+ e->enabled = b;
+}
+
+static void mainloop_defer_free(pa_defer_event *e) {
+ assert(e);
+ e->dead = e->mainloop->defer_events_scan_dead = 1;
+
+ if (e->enabled) {
+ e->enabled = 0;
+ assert(e->mainloop->deferred_pending > 0);
+ e->mainloop->deferred_pending--;
+ }
+}
+
+static void mainloop_defer_set_destroy(pa_defer_event *e, void (*callback)(pa_mainloop_api*a, pa_defer_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* Time events */
+static pa_time_event* mainloop_time_new(pa_mainloop_api*a, const struct timeval *tv, void (*callback) (pa_mainloop_api*a, pa_time_event*e, const struct timeval *tv, void *userdata), void *userdata) {
+ pa_mainloop *m;
+ pa_time_event *e;
+
+ assert(a && a->userdata && callback);
+ m = a->userdata;
+ assert(a == &m->api);
+
+ pa_mainloop_wakeup(m);
+
+ e = pa_xmalloc(sizeof(pa_time_event));
+ e->mainloop = m;
+ e->dead = 0;
+
+ e->enabled = !!tv;
+ if (tv)
+ e->timeval = *tv;
+
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ pa_idxset_put(m->time_events, e, NULL);
+
+ return e;
+}
+
+static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) {
+ assert(e);
+
+ pa_mainloop_wakeup(e->mainloop);
+
+ if (tv) {
+ e->enabled = 1;
+ e->timeval = *tv;
+ } else
+ e->enabled = 0;
+}
+
+static void mainloop_time_free(pa_time_event *e) {
+ assert(e);
+
+ pa_mainloop_wakeup(e->mainloop);
+
+ e->dead = e->mainloop->time_events_scan_dead = 1;
+}
+
+static void mainloop_time_set_destroy(pa_time_event *e, void (*callback)(pa_mainloop_api*a, pa_time_event *e, void *userdata)) {
+ assert(e);
+ e->destroy_callback = callback;
+}
+
+/* quit() */
+
+static void mainloop_quit(pa_mainloop_api*a, int retval) {
+ pa_mainloop *m;
+ assert(a && a->userdata);
+ m = a->userdata;
+ assert(a == &m->api);
+
+ pa_mainloop_wakeup(m);
+
+ m->quit = 1;
+ m->retval = retval;
+}
+
+static const pa_mainloop_api vtable = {
+ .userdata = NULL,
+
+ .io_new= mainloop_io_new,
+ .io_enable= mainloop_io_enable,
+ .io_free= mainloop_io_free,
+ .io_set_destroy= mainloop_io_set_destroy,
+
+ .time_new = mainloop_time_new,
+ .time_restart = mainloop_time_restart,
+ .time_free = mainloop_time_free,
+ .time_set_destroy = mainloop_time_set_destroy,
+
+ .defer_new = mainloop_defer_new,
+ .defer_enable = mainloop_defer_enable,
+ .defer_free = mainloop_defer_free,
+ .defer_set_destroy = mainloop_defer_set_destroy,
+
+ .quit = mainloop_quit,
+};
+
+pa_mainloop *pa_mainloop_new(void) {
+ pa_mainloop *m;
+
+ m = pa_xmalloc(sizeof(pa_mainloop));
+
+#ifndef OS_ISWIN32
+ if (pipe(m->wakeup_pipe) < 0) {
+ pa_xfree(m);
+ return NULL;
+ }
+
+ pa_make_nonblock_fd(m->wakeup_pipe[0]);
+ pa_make_nonblock_fd(m->wakeup_pipe[1]);
+#else
+ m->wakeup_pipe[0] = -1;
+ m->wakeup_pipe[1] = -1;
+#endif
+
+ m->io_events = pa_idxset_new(NULL, NULL);
+ m->defer_events = pa_idxset_new(NULL, NULL);
+ m->time_events = pa_idxset_new(NULL, NULL);
+
+ assert(m->io_events && m->defer_events && m->time_events);
+
+ m->io_events_scan_dead = m->defer_events_scan_dead = m->time_events_scan_dead = 0;
+
+ m->pollfds = NULL;
+ m->max_pollfds = m->n_pollfds = m->rebuild_pollfds = 0;
+
+ m->quit = m->retval = 0;
+
+ m->api = vtable;
+ m->api.userdata = m;
+
+ m->deferred_pending = 0;
+
+ m->state = STATE_PASSIVE;
+
+ return m;
+}
+
+static int io_foreach(void *p, uint32_t PA_GCC_UNUSED idx, int *del, void*userdata) {
+ pa_io_event *e = p;
+ int *all = userdata;
+ assert(e && del && all);
+
+ if (!*all && !e->dead)
+ return 0;
+
+ if (e->destroy_callback)
+ e->destroy_callback(&e->mainloop->api, e, e->userdata);
+ pa_xfree(e);
+ *del = 1;
+ return 0;
+}
+
+static int time_foreach(void *p, uint32_t PA_GCC_UNUSED idx, int *del, void*userdata) {
+ pa_time_event *e = p;
+ int *all = userdata;
+ assert(e && del && all);
+
+ if (!*all && !e->dead)
+ return 0;
+
+ if (e->destroy_callback)
+ e->destroy_callback(&e->mainloop->api, e, e->userdata);
+ pa_xfree(e);
+ *del = 1;
+ return 0;
+}
+
+static int defer_foreach(void *p, PA_GCC_UNUSED uint32_t idx, int *del, void*userdata) {
+ pa_defer_event *e = p;
+ int *all = userdata;
+ assert(e && del && all);
+
+ if (!*all && !e->dead)
+ return 0;
+
+ if (e->destroy_callback)
+ e->destroy_callback(&e->mainloop->api, e, e->userdata);
+ pa_xfree(e);
+ *del = 1;
+ return 0;
+}
+
+void pa_mainloop_free(pa_mainloop* m) {
+ int all = 1;
+ assert(m && (m->state != STATE_POLLING));
+
+ pa_idxset_foreach(m->io_events, io_foreach, &all);
+ pa_idxset_foreach(m->time_events, time_foreach, &all);
+ pa_idxset_foreach(m->defer_events, defer_foreach, &all);
+
+ pa_idxset_free(m->io_events, NULL, NULL);
+ pa_idxset_free(m->time_events, NULL, NULL);
+ pa_idxset_free(m->defer_events, NULL, NULL);
+
+ pa_xfree(m->pollfds);
+
+ if (m->wakeup_pipe[0] >= 0)
+ close(m->wakeup_pipe[0]);
+ if (m->wakeup_pipe[1] >= 0)
+ close(m->wakeup_pipe[1]);
+
+ pa_xfree(m);
+}
+
+static void scan_dead(pa_mainloop *m) {
+ int all = 0;
+ assert(m);
+
+ if (m->io_events_scan_dead)
+ pa_idxset_foreach(m->io_events, io_foreach, &all);
+ if (m->time_events_scan_dead)
+ pa_idxset_foreach(m->time_events, time_foreach, &all);
+ if (m->defer_events_scan_dead)
+ pa_idxset_foreach(m->defer_events, defer_foreach, &all);
+
+ m->io_events_scan_dead = m->time_events_scan_dead = m->defer_events_scan_dead = 0;
+}
+
+static void rebuild_pollfds(pa_mainloop *m) {
+ pa_io_event*e;
+ struct pollfd *p;
+ uint32_t idx = PA_IDXSET_INVALID;
+ unsigned l;
+
+ l = pa_idxset_size(m->io_events) + 1;
+ if (m->max_pollfds < l) {
+ m->pollfds = pa_xrealloc(m->pollfds, sizeof(struct pollfd)*l);
+ m->max_pollfds = l;
+ }
+
+ m->n_pollfds = 0;
+ p = m->pollfds;
+
+ if (m->wakeup_pipe[0] >= 0) {
+ m->pollfds[0].fd = m->wakeup_pipe[0];
+ m->pollfds[0].events = POLLIN;
+ m->pollfds[0].revents = 0;
+ p++;
+ m->n_pollfds++;
+ }
+
+ for (e = pa_idxset_first(m->io_events, &idx); e; e = pa_idxset_next(m->io_events, &idx)) {
+ if (e->dead) {
+ e->pollfd = NULL;
+ continue;
+ }
+
+ e->pollfd = p;
+ p->fd = e->fd;
+ p->events =
+ ((e->events & PA_IO_EVENT_INPUT) ? POLLIN : 0) |
+ ((e->events & PA_IO_EVENT_OUTPUT) ? POLLOUT : 0) |
+ POLLHUP |
+ POLLERR;
+ p->revents = 0;
+
+ p++;
+ m->n_pollfds++;
+ }
+
+ m->rebuild_pollfds = 0;
+}
+
+static int dispatch_pollfds(pa_mainloop *m) {
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_io_event *e;
+ int r = 0;
+
+ for (e = pa_idxset_first(m->io_events, &idx); e && !m->quit; e = pa_idxset_next(m->io_events, &idx)) {
+ if (e->dead || !e->pollfd || !e->pollfd->revents)
+ continue;
+
+ assert(e->pollfd->fd == e->fd && e->callback);
+ e->callback(&m->api, e, e->fd,
+ (e->pollfd->revents & POLLHUP ? PA_IO_EVENT_HANGUP : 0) |
+ (e->pollfd->revents & POLLIN ? PA_IO_EVENT_INPUT : 0) |
+ (e->pollfd->revents & POLLOUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (e->pollfd->revents & POLLERR ? PA_IO_EVENT_ERROR : 0),
+ e->userdata);
+ e->pollfd->revents = 0;
+ r++;
+ }
+
+ return r;
+}
+
+static int dispatch_defer(pa_mainloop *m) {
+ uint32_t idx;
+ pa_defer_event *e;
+ int r = 0;
+
+ if (!m->deferred_pending)
+ return 0;
+
+ for (e = pa_idxset_first(m->defer_events, &idx); e && !m->quit; e = pa_idxset_next(m->defer_events, &idx)) {
+ if (e->dead || !e->enabled)
+ continue;
+
+ assert(e->callback);
+ e->callback(&m->api, e, e->userdata);
+ r++;
+ }
+
+ return r;
+}
+
+static int calc_next_timeout(pa_mainloop *m) {
+ uint32_t idx;
+ pa_time_event *e;
+ struct timeval now;
+ int t = -1;
+ int got_time = 0;
+
+ if (pa_idxset_isempty(m->time_events))
+ return -1;
+
+ for (e = pa_idxset_first(m->time_events, &idx); e; e = pa_idxset_next(m->time_events, &idx)) {
+ int tmp;
+
+ if (e->dead || !e->enabled)
+ continue;
+
+ /* Let's save a system call */
+ if (!got_time) {
+ pa_gettimeofday(&now);
+ got_time = 1;
+ }
+
+ if (e->timeval.tv_sec < now.tv_sec || (e->timeval.tv_sec == now.tv_sec && e->timeval.tv_usec <= now.tv_usec))
+ return 0;
+
+ tmp = (e->timeval.tv_sec - now.tv_sec)*1000;
+
+ if (e->timeval.tv_usec > now.tv_usec)
+ tmp += (e->timeval.tv_usec - now.tv_usec)/1000;
+ else
+ tmp -= (now.tv_usec - e->timeval.tv_usec)/1000;
+
+ if (tmp == 0)
+ return 0;
+ else if (t == -1 || tmp < t)
+ t = tmp;
+ }
+
+ return t;
+}
+
+static int dispatch_timeout(pa_mainloop *m) {
+ uint32_t idx;
+ pa_time_event *e;
+ struct timeval now;
+ int got_time = 0;
+ int r = 0;
+ assert(m);
+
+ if (pa_idxset_isempty(m->time_events))
+ return 0;
+
+ for (e = pa_idxset_first(m->time_events, &idx); e && !m->quit; e = pa_idxset_next(m->time_events, &idx)) {
+
+ if (e->dead || !e->enabled)
+ continue;
+
+ /* Let's save a system call */
+ if (!got_time) {
+ pa_gettimeofday(&now);
+ got_time = 1;
+ }
+
+ if (e->timeval.tv_sec < now.tv_sec || (e->timeval.tv_sec == now.tv_sec && e->timeval.tv_usec <= now.tv_usec)) {
+ assert(e->callback);
+
+ e->enabled = 0;
+ e->callback(&m->api, e, &e->timeval, e->userdata);
+
+ r++;
+ }
+ }
+
+ return r;
+}
+
+void pa_mainloop_wakeup(pa_mainloop *m) {
+ char c = 'W';
+ assert(m);
+
+ if (m->wakeup_pipe[1] >= 0)
+ write(m->wakeup_pipe[1], &c, sizeof(c));
+}
+
+static void clear_wakeup(pa_mainloop *m) {
+ char c[10];
+
+ assert(m);
+
+ if (m->wakeup_pipe[0] < 0)
+ return;
+
+ while (read(m->wakeup_pipe[0], &c, sizeof(c)) == sizeof(c));
+}
+
+int pa_mainloop_prepare(pa_mainloop *m, int timeout) {
+ int dispatched = 0;
+
+ assert(m && (m->state == STATE_PASSIVE));
+
+ clear_wakeup(m);
+
+ scan_dead(m);
+
+ if (m->quit)
+ goto quit;
+
+ dispatched += dispatch_defer(m);
+
+ if (m->quit)
+ goto quit;
+
+ if (m->rebuild_pollfds)
+ rebuild_pollfds(m);
+
+ m->prepared_timeout = calc_next_timeout(m);
+ if ((timeout >= 0) && (m->prepared_timeout > timeout))
+ m->prepared_timeout = timeout;
+
+ m->state = STATE_PREPARED;
+
+ return dispatched;
+
+quit:
+
+ m->state = STATE_QUIT;
+
+ return -2;
+}
+
+int pa_mainloop_poll(pa_mainloop *m) {
+ int r;
+
+ assert(m && (m->state == STATE_PREPARED));
+
+ m->state = STATE_POLLING;
+
+ r = poll(m->pollfds, m->n_pollfds, m->prepared_timeout);
+
+ if ((r < 0) && (errno == EINTR))
+ r = 0;
+
+ if (r < 0)
+ m->state = STATE_PASSIVE;
+ else
+ m->state = STATE_POLLED;
+
+ return r;
+}
+
+int pa_mainloop_dispatch(pa_mainloop *m) {
+ int dispatched = 0;
+
+ assert(m && (m->state == STATE_POLLED));
+
+ dispatched += dispatch_timeout(m);
+
+ if (m->quit)
+ goto quit;
+
+ dispatched += dispatch_pollfds(m);
+
+ if (m->quit)
+ goto quit;
+
+ m->state = STATE_PASSIVE;
+
+ return dispatched;
+
+quit:
+
+ m->state = STATE_QUIT;
+
+ return -2;
+}
+
+int pa_mainloop_get_retval(pa_mainloop *m) {
+ assert(m);
+ return m->retval;
+}
+
+int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) {
+ int r, dispatched = 0;
+
+ assert(m);
+
+ r = pa_mainloop_prepare(m, block ? -1 : 0);
+ if (r < 0) {
+ if ((r == -2) && retval)
+ *retval = pa_mainloop_get_retval(m);
+ return r;
+ }
+
+ dispatched += r;
+
+ r = pa_mainloop_poll(m);
+ if (r < 0) {
+ pa_log(__FILE__": poll(): %s\n", strerror(errno));
+ return r;
+ }
+
+ r = pa_mainloop_dispatch(m);
+ if (r < 0) {
+ if ((r == -2) && retval)
+ *retval = pa_mainloop_get_retval(m);
+ return r;
+ }
+
+ dispatched += r;
+
+ return dispatched;
+}
+
+int pa_mainloop_run(pa_mainloop *m, int *retval) {
+ int r;
+ while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0);
+
+ if (r == -2)
+ return 1;
+ else if (r < 0)
+ return -1;
+ else
+ return 0;
+}
+
+void pa_mainloop_quit(pa_mainloop *m, int r) {
+ assert(m);
+ pa_mainloop_wakeup(m);
+ m->quit = r;
+}
+
+pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m) {
+ assert(m);
+ return &m->api;
+}
+
+int pa_mainloop_deferred_pending(pa_mainloop *m) {
+ assert(m);
+ return m->deferred_pending > 0;
+}
+
+
+#if 0
+void pa_mainloop_dump(pa_mainloop *m) {
+ assert(m);
+
+ pa_log(__FILE__": Dumping mainloop sources START\n");
+
+ {
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_io_event *e;
+ for (e = pa_idxset_first(m->io_events, &idx); e; e = pa_idxset_next(m->io_events, &idx)) {
+ if (e->dead)
+ continue;
+
+ pa_log(__FILE__": kind=io fd=%i events=%i callback=%p userdata=%p\n", e->fd, (int) e->events, (void*) e->callback, (void*) e->userdata);
+ }
+ }
+ {
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_defer_event *e;
+ for (e = pa_idxset_first(m->defer_events, &idx); e; e = pa_idxset_next(m->defer_events, &idx)) {
+ if (e->dead)
+ continue;
+
+ pa_log(__FILE__": kind=defer enabled=%i callback=%p userdata=%p\n", e->enabled, (void*) e->callback, (void*) e->userdata);
+ }
+ }
+ {
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_time_event *e;
+ for (e = pa_idxset_first(m->time_events, &idx); e; e = pa_idxset_next(m->time_events, &idx)) {
+ if (e->dead)
+ continue;
+
+ pa_log(__FILE__": kind=time enabled=%i time=%lu.%lu callback=%p userdata=%p\n", e->enabled, (unsigned long) e->timeval.tv_sec, (unsigned long) e->timeval.tv_usec, (void*) e->callback, (void*) e->userdata);
+ }
+ }
+
+ pa_log(__FILE__": Dumping mainloop sources STOP\n");
+
+}
+#endif
diff --git a/src/polyp/mainloop.h b/src/polyp/mainloop.h
new file mode 100644
index 00000000..691f8c50
--- /dev/null
+++ b/src/polyp/mainloop.h
@@ -0,0 +1,90 @@
+#ifndef foomainloophfoo
+#define foomainloophfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/mainloop-api.h>
+#include <polyp/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+/** \file
+ *
+ * A minimal main loop implementation based on the C library's poll()
+ * function. Using the routines defined herein you may create a simple
+ * main loop supporting the generic main loop abstraction layer as
+ * defined in \ref mainloop-api.h. This implementation is thread safe
+ * as long as you access the main loop object from a single thread only.*/
+
+/** \pa_mainloop
+ * An opaque main loop object
+ */
+typedef struct pa_mainloop pa_mainloop;
+
+/** Allocate a new main loop object */
+pa_mainloop *pa_mainloop_new(void);
+
+/** Free a main loop object */
+void pa_mainloop_free(pa_mainloop* m);
+
+
+/** Prepare for a single iteration of the main loop. Returns a negative value
+on error or exit request. timeout specifies a maximum timeout for the subsequent
+poll, or -1 for blocking behaviour. Defer events are also dispatched when this
+function is called. On success returns the number of source dispatched in this
+iteration.*/
+int pa_mainloop_prepare(pa_mainloop *m, int timeout);
+/** Execute the previously prepared poll. Returns a negative value on error.*/
+int pa_mainloop_poll(pa_mainloop *m);
+/** Dispatch timeout and io events from the previously executed poll. Returns
+a negative value on error. On success returns the number of source dispatched. */
+int pa_mainloop_dispatch(pa_mainloop *m);
+
+/** Return the return value as specified with the main loop's quit() routine. */
+int pa_mainloop_get_retval(pa_mainloop *m);
+
+/** Run a single iteration of the main loop. This is a convenience function
+for pa_mainloop_prepare(), pa_mainloop_poll() and pa_mainloop_dispatch().
+Returns a negative value on error or exit request. If block is nonzero,
+block for events if none are queued. Optionally return the return value as
+specified with the main loop's quit() routine in the integer variable retval points
+to. On success returns the number of source dispatched in this iteration. */
+int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval);
+
+/** Run unlimited iterations of the main loop object until the main loop's quit() routine is called. */
+int pa_mainloop_run(pa_mainloop *m, int *retval);
+
+/** Return the abstract main loop abstraction layer vtable for this main loop. This calls pa_mainloop_iterate() iteratively.*/
+pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m);
+
+/** Return non-zero when there are any deferred events pending. \since 0.5 */
+int pa_mainloop_deferred_pending(pa_mainloop *m);
+
+/** Shutdown the main loop */
+void pa_mainloop_quit(pa_mainloop *m, int r);
+
+/** Interrupt a running poll (for threaded systems) */
+void pa_mainloop_wakeup(pa_mainloop *m);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-browser.c b/src/polyp/polyplib-browser.c
new file mode 100644
index 00000000..9a389484
--- /dev/null
+++ b/src/polyp/polyplib-browser.c
@@ -0,0 +1,312 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the
+ License, or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <assert.h>
+#include <howl.h>
+
+#include "polyplib-browser.h"
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/util.h>
+
+#define SERVICE_NAME_SINK "_polypaudio-sink._tcp."
+#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp."
+#define SERVICE_NAME_SERVER "_polypaudio-server._tcp."
+
+pa_browser {
+ int ref;
+ pa_mainloop_api *mainloop;
+
+ void (*callback)(pa_browser *z, pa_browse_opcode c, const pa_browse_info *i, void *userdata);
+ void *callback_userdata;
+
+ sw_discovery discovery;
+ pa_io_event *io_event;
+};
+
+
+static void io_callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags events, void *userdata) {
+ pa_browser *b = userdata;
+ assert(a && b && b->mainloop == a);
+
+ if (events != PA_IO_EVENT_INPUT || sw_discovery_read_socket(b->discovery) != SW_OKAY) {
+ pa_log(__FILE__": connection to HOWL daemon failed.\n");
+ b->mainloop->io_free(b->io_event);
+ b->io_event = NULL;
+ return;
+ }
+}
+
+static sw_result resolve_reply(
+ sw_discovery discovery,
+ sw_discovery_oid oid,
+ sw_uint32 interface_index,
+ sw_const_string name,
+ sw_const_string type,
+ sw_const_string domain,
+ sw_ipv4_address address,
+ sw_port port,
+ sw_octets text_record,
+ sw_ulong text_record_len,
+ sw_opaque extra) {
+
+ pa_browser *b = extra;
+ pa_browse_info i;
+ char ip[256], a[256];
+ pa_browse_opcode opcode;
+ int device_found = 0;
+ uint32_t cookie;
+ pa_typeid_t typeid;
+ pa_sample_spec ss;
+ int ss_valid = 0;
+ sw_text_record_iterator iterator;
+ int free_iterator = 0;
+ char *c = NULL;
+
+ assert(b);
+
+ sw_discovery_cancel(discovery, oid);
+
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+
+ if (!b->callback)
+ goto fail;
+
+ if (!strcmp(type, SERVICE_NAME_SINK))
+ opcode = PA_BROWSE_NEW_SINK;
+ else if (!strcmp(type, SERVICE_NAME_SOURCE))
+ opcode = PA_BROWSE_NEW_SOURCE;
+ else if (!strcmp(type, SERVICE_NAME_SERVER))
+ opcode = PA_BROWSE_NEW_SERVER;
+ else
+ goto fail;
+
+
+ snprintf(a, sizeof(a), "tcp:%s:%u", sw_ipv4_address_name(address, ip, sizeof(ip)), port);
+ i.server = a;
+
+ if (text_record && text_record_len) {
+ char key[SW_TEXT_RECORD_MAX_LEN];
+ uint8_t val[SW_TEXT_RECORD_MAX_LEN];
+ uint32_t val_len;
+
+ if (sw_text_record_iterator_init(&iterator, text_record, text_record_len) != SW_OKAY) {
+ pa_log("sw_text_record_string_iterator_init() failed.\n");
+ goto fail;
+ }
+
+ free_iterator = 1;
+
+ while (sw_text_record_iterator_next(iterator, key, val, &val_len) == SW_OKAY) {
+ c = pa_xstrndup((char*) val, val_len);
+
+ if (!strcmp(key, "device")) {
+ device_found = 1;
+ pa_xfree((char*) i.device);
+ i.device = c;
+ c = NULL;
+ } else if (!strcmp(key, "server-version")) {
+ pa_xfree((char*) i.server_version);
+ i.server_version = c;
+ c = NULL;
+ } else if (!strcmp(key, "user-name")) {
+ pa_xfree((char*) i.user_name);
+ i.user_name = c;
+ c = NULL;
+ } else if (!strcmp(key, "fqdn")) {
+ size_t l;
+
+ pa_xfree((char*) i.fqdn);
+ i.fqdn = c;
+ c = NULL;
+
+ l = strlen(a);
+ assert(l+1 <= sizeof(a));
+ strncat(a, " ", sizeof(a)-l-1);
+ strncat(a, i.fqdn, sizeof(a)-l-2);
+ } else if (!strcmp(key, "cookie")) {
+
+ if (pa_atou(c, &cookie) < 0)
+ goto fail;
+
+ i.cookie = &cookie;
+ } else if (!strcmp(key, "description")) {
+ pa_xfree((char*) i.description);
+ i.description = c;
+ c = NULL;
+ } else if (!strcmp(key, "typeid")) {
+
+ if (pa_atou(c, &typeid) < 0)
+ goto fail;
+
+ i.typeid = &typeid;
+ } else if (!strcmp(key, "channels")) {
+ uint32_t ch;
+
+ if (pa_atou(c, &ch) < 0 || ch <= 0 || ch > 255)
+ goto fail;
+
+ ss.channels = (uint8_t) ch;
+ ss_valid |= 1;
+
+ } else if (!strcmp(key, "rate")) {
+ if (pa_atou(c, &ss.rate) < 0)
+ goto fail;
+ ss_valid |= 2;
+ } else if (!strcmp(key, "format")) {
+
+ if ((ss.format = pa_parse_sample_format(c)) == PA_SAMPLE_INVALID)
+ goto fail;
+
+ ss_valid |= 4;
+ }
+
+ pa_xfree(c);
+ c = NULL;
+ }
+
+ }
+
+ /* No device txt record was sent for a sink or source service */
+ if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
+ goto fail;
+
+ if (ss_valid == 7)
+ i.sample_spec = &ss;
+
+
+ b->callback(b, opcode, &i, b->callback_userdata);
+
+fail:
+ pa_xfree((void*) i.device);
+ pa_xfree((void*) i.fqdn);
+ pa_xfree((void*) i.server_version);
+ pa_xfree((void*) i.user_name);
+ pa_xfree((void*) i.description);
+ pa_xfree(c);
+
+ if (free_iterator)
+ sw_text_record_iterator_fina(iterator);
+
+
+ return SW_OKAY;
+}
+
+static sw_result browse_reply(
+ sw_discovery discovery,
+ sw_discovery_oid id,
+ sw_discovery_browse_status status,
+ sw_uint32 interface_index,
+ sw_const_string name,
+ sw_const_string type,
+ sw_const_string domain,
+ sw_opaque extra) {
+
+ pa_browser *b = extra;
+ assert(b);
+
+ switch (status) {
+ case SW_DISCOVERY_BROWSE_ADD_SERVICE: {
+ sw_discovery_oid oid;
+
+ if (sw_discovery_resolve(b->discovery, 0, name, type, domain, resolve_reply, b, &oid) != SW_OKAY)
+ pa_log("sw_discovery_resolve() failed\n");
+
+ break;
+ }
+
+ case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
+ if (b->callback) {
+ pa_browse_info i;
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+ b->callback(b, PA_BROWSE_REMOVE, &i, b->callback_userdata);
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ return SW_OKAY;
+}
+
+pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
+ pa_browser *b;
+ sw_discovery_oid oid;
+
+ b = pa_xmalloc(sizeof(pa_browser));
+ b->mainloop = mainloop;
+ b->ref = 1;
+ b->callback = NULL;
+ b->callback_userdata = NULL;
+
+ if (sw_discovery_init(&b->discovery) != SW_OKAY) {
+ pa_log("sw_discovery_init() failed.\n");
+ pa_xfree(b);
+ return NULL;
+ }
+
+ if (sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SERVER, NULL, browse_reply, b, &oid) != SW_OKAY ||
+ sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SINK, NULL, browse_reply, b, &oid) != SW_OKAY ||
+ sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SOURCE, NULL, browse_reply, b, &oid) != SW_OKAY) {
+
+ pa_log("sw_discovery_browse() failed.\n");
+
+ sw_discovery_fina(b->discovery);
+ pa_xfree(b);
+ return NULL;
+ }
+
+ b->io_event = mainloop->io_new(mainloop, sw_discovery_socket(b->discovery), PA_IO_EVENT_INPUT, io_callback, b);
+ return b;
+}
+
+static void browser_free(pa_browser *b) {
+ assert(b && b->mainloop);
+
+ if (b->io_event)
+ b->mainloop->io_free(b->io_event);
+
+ sw_discovery_fina(b->discovery);
+ pa_xfree(b);
+}
+
+pa_browser *pa_browser_ref(pa_browser *b) {
+ assert(b && b->ref >= 1);
+ b->ref++;
+ return b;
+}
+
+void pa_browser_unref(pa_browser *b) {
+ assert(b && b->ref >= 1);
+
+ if ((-- (b->ref)) <= 0)
+ browser_free(b);
+}
+
+void pa_browser_set_callback(pa_browser *b, void (*cb)(pa_browser *z, pa_browse_opcode c, const pa_browse_info *i, void* userdata), void *userdata) {
+ assert(b);
+
+ b->callback = cb;
+ b->callback_userdata = userdata;
+}
diff --git a/src/polyp/polyplib-browser.h b/src/polyp/polyplib-browser.h
new file mode 100644
index 00000000..853304d7
--- /dev/null
+++ b/src/polyp/polyplib-browser.h
@@ -0,0 +1,65 @@
+#ifndef foopolyplibbrowserhfoo
+#define foopolyplibbrowserhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the
+ License, or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/mainloop-api.h>
+#include <polyp/sample.h>
+#include <polyp/cdecl.h>
+#include <polyp/typeid.h>
+
+PA_C_DECL_BEGIN
+
+pa_browser;
+
+pa_browse_opcode {
+ PA_BROWSE_NEW_SERVER,
+ PA_BROWSE_NEW_SINK,
+ PA_BROWSE_NEW_SOURCE,
+ PA_BROWSE_REMOVE
+};
+
+pa_browser *pa_browser_new(pa_mainloop_api *mainloop);
+pa_browser *pa_browser_ref(pa_browser *z);
+void pa_browser_unref(pa_browser *z);
+
+pa_browse_info {
+ /* Unique service name */
+ const char *name; /* always available */
+
+ /* Server info */
+ const char *server; /* always available */
+ const char *server_version, *user_name, *fqdn; /* optional */
+ const uint32_t *cookie; /* optional */
+
+ /* Device info */
+ const char *device; /* always available when this information is of a sink/source */
+ const char *description; /* optional */
+ const pa_typeid_t *typeid; /* optional */
+ const pa_sample_spec *sample_spec; /* optional */
+};
+
+void pa_browser_set_callback(pa_browser *z, void (*cb)(pa_browser *z, pa_browse_opcode c, const pa_browse_info *i, void *userdata), void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-context.c b/src/polyp/polyplib-context.c
new file mode 100644
index 00000000..c392f0fc
--- /dev/null
+++ b/src/polyp/polyplib-context.c
@@ -0,0 +1,871 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <limits.h>
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#include <polypcore/winsock.h>
+
+#include "polyplib-internal.h"
+#include "polyplib-context.h"
+#include "polyplib-version.h"
+#include <polypcore/native-common.h>
+#include <polypcore/pdispatch.h>
+#include <polypcore/pstream.h>
+#include <polypcore/dynarray.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/pstream-util.h>
+#include <polypcore/util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polyp/client-conf.h>
+#include <polypcore/socket-util.h>
+
+#ifdef HAVE_X11
+#include "client-conf-x11.h"
+#endif
+
+#define AUTOSPAWN_LOCK "autospawn.lock"
+
+static const pa_pdispatch_callback command_table[PA_COMMAND_MAX] = {
+ [PA_COMMAND_REQUEST] = pa_command_request,
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed,
+ [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed,
+ [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event
+};
+
+static void unlock_autospawn_lock_file(pa_context *c) {
+ assert(c);
+
+ if (c->autospawn_lock_fd >= 0) {
+ char lf[PATH_MAX];
+ pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
+
+ pa_unlock_lockfile(lf, c->autospawn_lock_fd);
+ c->autospawn_lock_fd = -1;
+ }
+}
+
+pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {
+ pa_context *c;
+ assert(mainloop && name);
+
+ c = pa_xmalloc(sizeof(pa_context));
+ c->ref = 1;
+ c->name = pa_xstrdup(name);
+ c->mainloop = mainloop;
+ c->client = NULL;
+ c->pstream = NULL;
+ c->pdispatch = NULL;
+ c->playback_streams = pa_dynarray_new();
+ c->record_streams = pa_dynarray_new();
+ assert(c->playback_streams && c->record_streams);
+
+ PA_LLIST_HEAD_INIT(pa_stream, c->streams);
+ PA_LLIST_HEAD_INIT(pa_operation, c->operations);
+
+ c->error = PA_ERROR_OK;
+ c->state = PA_CONTEXT_UNCONNECTED;
+ c->ctag = 0;
+
+ c->state_callback = NULL;
+ c->state_userdata = NULL;
+
+ c->subscribe_callback = NULL;
+ c->subscribe_userdata = NULL;
+
+ c->memblock_stat = pa_memblock_stat_new();
+ c->local = -1;
+ c->server_list = NULL;
+ c->server = NULL;
+ c->autospawn_lock_fd = -1;
+ memset(&c->spawn_api, 0, sizeof(c->spawn_api));
+ c->do_autospawn = 0;
+
+#ifdef SIGPIPE
+ pa_check_signal_is_blocked(SIGPIPE);
+#endif
+
+ c->conf = pa_client_conf_new();
+ pa_client_conf_load(c->conf, NULL);
+#ifdef HAVE_X11
+ pa_client_conf_from_x11(c->conf, NULL);
+#endif
+ pa_client_conf_env(c->conf);
+
+ return c;
+}
+
+static void context_free(pa_context *c) {
+ assert(c);
+
+ unlock_autospawn_lock_file(c);
+
+ while (c->operations)
+ pa_operation_cancel(c->operations);
+
+ while (c->streams)
+ pa_stream_set_state(c->streams, PA_STREAM_TERMINATED);
+
+ if (c->client)
+ pa_socket_client_unref(c->client);
+ if (c->pdispatch)
+ pa_pdispatch_unref(c->pdispatch);
+ if (c->pstream) {
+ pa_pstream_close(c->pstream);
+ pa_pstream_unref(c->pstream);
+ }
+
+ if (c->record_streams)
+ pa_dynarray_free(c->record_streams, NULL, NULL);
+ if (c->playback_streams)
+ pa_dynarray_free(c->playback_streams, NULL, NULL);
+
+ pa_memblock_stat_unref(c->memblock_stat);
+
+ if (c->conf)
+ pa_client_conf_free(c->conf);
+
+ pa_strlist_free(c->server_list);
+
+ pa_xfree(c->name);
+ pa_xfree(c->server);
+ pa_xfree(c);
+}
+
+pa_context* pa_context_ref(pa_context *c) {
+ assert(c && c->ref >= 1);
+ c->ref++;
+ return c;
+}
+
+void pa_context_unref(pa_context *c) {
+ assert(c && c->ref >= 1);
+
+ if ((--(c->ref)) == 0)
+ context_free(c);
+}
+
+void pa_context_set_state(pa_context *c, pa_context_state_t st) {
+ assert(c);
+
+ if (c->state == st)
+ return;
+
+ pa_context_ref(c);
+
+ if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) {
+ pa_stream *s;
+
+ s = c->streams ? pa_stream_ref(c->streams) : NULL;
+ while (s) {
+ pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL;
+ pa_stream_set_state(s, st == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED);
+ pa_stream_unref(s);
+ s = n;
+ }
+
+ if (c->pdispatch)
+ pa_pdispatch_unref(c->pdispatch);
+ c->pdispatch = NULL;
+
+ if (c->pstream) {
+ pa_pstream_close(c->pstream);
+ pa_pstream_unref(c->pstream);
+ }
+ c->pstream = NULL;
+
+ if (c->client)
+ pa_socket_client_unref(c->client);
+ c->client = NULL;
+ }
+
+ c->state = st;
+ if (c->state_callback)
+ c->state_callback(c, c->state_userdata);
+
+ pa_context_unref(c);
+}
+
+void pa_context_fail(pa_context *c, int error) {
+ assert(c);
+ c->error = error;
+ pa_context_set_state(c, PA_CONTEXT_FAILED);
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ pa_context *c = userdata;
+ assert(p && c);
+ pa_context_fail(c, PA_ERROR_CONNECTIONTERMINATED);
+}
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, void *userdata) {
+ pa_context *c = userdata;
+ assert(p && packet && c);
+
+ pa_context_ref(c);
+
+ if (pa_pdispatch_run(c->pdispatch, packet, c) < 0) {
+ pa_log(__FILE__": invalid packet.\n");
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ }
+
+ pa_context_unref(c);
+}
+
+static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, PA_GCC_UNUSED uint32_t delta, const pa_memchunk *chunk, void *userdata) {
+ pa_context *c = userdata;
+ pa_stream *s;
+ assert(p && chunk && c && chunk->memblock && chunk->memblock->data);
+
+ pa_context_ref(c);
+
+ if ((s = pa_dynarray_get(c->record_streams, channel))) {
+ pa_mcalign_push(s->mcalign, chunk);
+
+ for (;;) {
+ pa_memchunk t;
+
+ if (pa_mcalign_pop(s->mcalign, &t) < 0)
+ break;
+
+ if (s->read_callback) {
+ s->read_callback(s, (uint8_t*) t.memblock->data + t.index, t.length, s->read_userdata);
+ s->counter += chunk->length;
+ }
+
+ pa_memblock_unref(t.memblock);
+ }
+ }
+
+ pa_context_unref(c);
+}
+
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t) {
+ assert(c);
+
+ if (command == PA_COMMAND_ERROR) {
+ assert(t);
+
+ if (pa_tagstruct_getu32(t, &c->error) < 0) {
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ return -1;
+
+ }
+ } else if (command == PA_COMMAND_TIMEOUT)
+ c->error = PA_ERROR_TIMEOUT;
+ else {
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ assert(pd && c && (c->state == PA_CONTEXT_AUTHORIZING || c->state == PA_CONTEXT_SETTING_NAME));
+
+ pa_context_ref(c);
+
+ if (command != PA_COMMAND_REPLY) {
+
+ if (pa_context_handle_error(c, command, t) < 0)
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+
+ pa_context_fail(c, c->error);
+ goto finish;
+ }
+
+ switch(c->state) {
+ case PA_CONTEXT_AUTHORIZING: {
+ pa_tagstruct *reply;
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
+ pa_tagstruct_putu32(reply, tag = c->ctag++);
+ pa_tagstruct_puts(reply, c->name);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c);
+
+ pa_context_set_state(c, PA_CONTEXT_SETTING_NAME);
+ break;
+ }
+
+ case PA_CONTEXT_SETTING_NAME :
+ pa_context_set_state(c, PA_CONTEXT_READY);
+ break;
+
+ default:
+ assert(0);
+ }
+
+finish:
+ pa_context_unref(c);
+}
+
+static void setup_context(pa_context *c, pa_iochannel *io) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && io);
+
+ pa_context_ref(c);
+
+ assert(!c->pstream);
+ c->pstream = pa_pstream_new(c->mainloop, io, c->memblock_stat);
+ assert(c->pstream);
+
+ pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c);
+ pa_pstream_set_recieve_packet_callback(c->pstream, pstream_packet_callback, c);
+ pa_pstream_set_recieve_memblock_callback(c->pstream, pstream_memblock_callback, c);
+
+ assert(!c->pdispatch);
+ c->pdispatch = pa_pdispatch_new(c->mainloop, command_table, PA_COMMAND_MAX);
+ assert(c->pdispatch);
+
+ if (!c->conf->cookie_valid) {
+ pa_context_fail(c, PA_ERROR_AUTHKEY);
+ goto finish;
+ }
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, PA_COMMAND_AUTH);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_put_arbitrary(t, c->conf->cookie, sizeof(c->conf->cookie));
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c);
+
+ pa_context_set_state(c, PA_CONTEXT_AUTHORIZING);
+
+finish:
+
+ pa_context_unref(c);
+}
+
+static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata);
+
+#ifndef OS_IS_WIN32
+
+static int context_connect_spawn(pa_context *c) {
+ pid_t pid;
+ int status, r;
+ int fds[2] = { -1, -1} ;
+ pa_iochannel *io;
+
+ pa_context_ref(c);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ pa_log(__FILE__": socketpair() failed: %s\n", strerror(errno));
+ pa_context_fail(c, PA_ERROR_INTERNAL);
+ goto fail;
+ }
+
+ pa_fd_set_cloexec(fds[0], 1);
+
+ pa_socket_low_delay(fds[0]);
+ pa_socket_low_delay(fds[1]);
+
+ if (c->spawn_api.prefork)
+ c->spawn_api.prefork();
+
+ if ((pid = fork()) < 0) {
+ pa_log(__FILE__": fork() failed: %s\n", strerror(errno));
+ pa_context_fail(c, PA_ERROR_INTERNAL);
+
+ if (c->spawn_api.postfork)
+ c->spawn_api.postfork();
+
+ goto fail;
+ } else if (!pid) {
+ /* Child */
+
+ char t[128];
+ const char *state = NULL;
+#define MAX_ARGS 64
+ const char * argv[MAX_ARGS+1];
+ int n;
+
+ /* Not required, since fds[0] has CLOEXEC enabled anyway */
+ close(fds[0]);
+
+ if (c->spawn_api.atfork)
+ c->spawn_api.atfork();
+
+ /* Setup argv */
+
+ n = 0;
+
+ argv[n++] = c->conf->daemon_binary;
+ argv[n++] = "--daemonize=yes";
+
+ snprintf(t, sizeof(t), "-Lmodule-native-protocol-fd fd=%i", fds[1]);
+ argv[n++] = strdup(t);
+
+ while (n < MAX_ARGS) {
+ char *a;
+
+ if (!(a = pa_split_spaces(c->conf->extra_arguments, &state)))
+ break;
+
+ argv[n++] = a;
+ }
+
+ argv[n++] = NULL;
+
+ execv(argv[0], (char * const *) argv);
+ _exit(1);
+#undef MAX_ARGS
+ }
+
+ /* Parent */
+
+ r = waitpid(pid, &status, 0);
+
+ if (c->spawn_api.postfork)
+ c->spawn_api.postfork();
+
+ if (r < 0) {
+ pa_log(__FILE__": waitpid() failed: %s\n", strerror(errno));
+ pa_context_fail(c, PA_ERROR_INTERNAL);
+ goto fail;
+ } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ pa_context_fail(c, PA_ERROR_CONNECTIONREFUSED);
+ goto fail;
+ }
+
+ close(fds[1]);
+
+ c->local = 1;
+
+ io = pa_iochannel_new(c->mainloop, fds[0], fds[0]);
+
+ setup_context(c, io);
+ unlock_autospawn_lock_file(c);
+
+ pa_context_unref(c);
+
+ return 0;
+
+fail:
+ if (fds[0] != -1)
+ close(fds[0]);
+ if (fds[1] != -1)
+ close(fds[1]);
+
+ unlock_autospawn_lock_file(c);
+
+ pa_context_unref(c);
+
+ return -1;
+}
+
+#endif /* OS_IS_WIN32 */
+
+static int try_next_connection(pa_context *c) {
+ char *u = NULL;
+ int r = -1;
+ assert(c && !c->client);
+
+ for (;;) {
+ if (u)
+ pa_xfree(u);
+ u = NULL;
+
+ c->server_list = pa_strlist_pop(c->server_list, &u);
+
+ if (!u) {
+
+#ifndef OS_IS_WIN32
+ if (c->do_autospawn) {
+ r = context_connect_spawn(c);
+ goto finish;
+ }
+#endif
+
+ pa_context_fail(c, PA_ERROR_CONNECTIONREFUSED);
+ goto finish;
+ }
+
+ pa_log_debug(__FILE__": Trying to connect to %s...\n", u);
+
+ pa_xfree(c->server);
+ c->server = pa_xstrdup(u);
+
+ if (!(c->client = pa_socket_client_new_string(c->mainloop, u, PA_NATIVE_DEFAULT_PORT)))
+ continue;
+
+ c->local = pa_socket_client_is_local(c->client);
+ pa_socket_client_set_callback(c->client, on_connection, c);
+ break;
+ }
+
+ r = 0;
+
+finish:
+ if (u)
+ pa_xfree(u);
+
+ return r;
+}
+
+static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) {
+ pa_context *c = userdata;
+ assert(client && c && c->state == PA_CONTEXT_CONNECTING);
+
+ pa_context_ref(c);
+
+ pa_socket_client_unref(client);
+ c->client = NULL;
+
+ if (!io) {
+ /* Try the item in the list */
+ if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == EHOSTUNREACH) {
+ try_next_connection(c);
+ goto finish;
+ }
+
+ pa_context_fail(c, PA_ERROR_CONNECTIONREFUSED);
+ goto finish;
+ }
+
+ unlock_autospawn_lock_file(c);
+ setup_context(c, io);
+
+finish:
+ pa_context_unref(c);
+}
+
+int pa_context_connect(pa_context *c, const char *server, int spawn, const pa_spawn_api *api) {
+ int r = -1;
+ assert(c && c->ref >= 1 && c->state == PA_CONTEXT_UNCONNECTED);
+
+ if (!server)
+ server = c->conf->default_server;
+
+ pa_context_ref(c);
+
+ assert(!c->server_list);
+
+ if (server) {
+ if (!(c->server_list = pa_strlist_parse(server))) {
+ pa_context_fail(c, PA_ERROR_INVALIDSERVER);
+ goto finish;
+ }
+ } else {
+ char *d;
+ char ufn[PATH_MAX];
+
+ /* Prepend in reverse order */
+
+ if ((d = getenv("DISPLAY"))) {
+ char *e;
+ d = pa_xstrdup(d);
+ if ((e = strchr(d, ':')))
+ *e = 0;
+
+ if (*d)
+ c->server_list = pa_strlist_prepend(c->server_list, d);
+
+ pa_xfree(d);
+ }
+
+ c->server_list = pa_strlist_prepend(c->server_list, "tcp6:localhost");
+ c->server_list = pa_strlist_prepend(c->server_list, "localhost");
+ c->server_list = pa_strlist_prepend(c->server_list, pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET, ufn, sizeof(ufn)));
+
+ /* Wrap the connection attempts in a single transaction for sane autospawn locking */
+ if (spawn && c->conf->autospawn) {
+ char lf[PATH_MAX];
+
+ pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
+ pa_make_secure_parent_dir(lf);
+ assert(c->autospawn_lock_fd <= 0);
+ c->autospawn_lock_fd = pa_lock_lockfile(lf);
+
+ if (api)
+ c->spawn_api = *api;
+ c->do_autospawn = 1;
+ }
+
+ }
+
+ pa_context_set_state(c, PA_CONTEXT_CONNECTING);
+ r = try_next_connection(c);
+
+finish:
+ pa_context_unref(c);
+
+ return r;
+}
+
+void pa_context_disconnect(pa_context *c) {
+ assert(c);
+ pa_context_set_state(c, PA_CONTEXT_TERMINATED);
+}
+
+pa_context_state_t pa_context_get_state(pa_context *c) {
+ assert(c && c->ref >= 1);
+ return c->state;
+}
+
+int pa_context_errno(pa_context *c) {
+ assert(c && c->ref >= 1);
+ return c->error;
+}
+
+void pa_context_set_state_callback(pa_context *c, void (*cb)(pa_context *c, void *userdata), void *userdata) {
+ assert(c && c->ref >= 1);
+ c->state_callback = cb;
+ c->state_userdata = userdata;
+}
+
+int pa_context_is_pending(pa_context *c) {
+ assert(c && c->ref >= 1);
+
+/* pa_log("pstream: %i\n", pa_pstream_is_pending(c->pstream)); */
+/* pa_log("pdispatch: %i\n", pa_pdispatch_is_pending(c->pdispatch)); */
+
+ return (c->pstream && pa_pstream_is_pending(c->pstream)) ||
+ (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) ||
+ c->client;
+}
+
+static void set_dispatch_callbacks(pa_operation *o);
+
+static void pdispatch_drain_callback(PA_GCC_UNUSED pa_pdispatch*pd, void *userdata) {
+ set_dispatch_callbacks(userdata);
+}
+
+static void pstream_drain_callback(PA_GCC_UNUSED pa_pstream *s, void *userdata) {
+ set_dispatch_callbacks(userdata);
+}
+
+static void set_dispatch_callbacks(pa_operation *o) {
+ int done = 1;
+ assert(o && o->context && o->context->ref >= 1 && o->ref >= 1 && o->context->state == PA_CONTEXT_READY);
+
+ pa_pstream_set_drain_callback(o->context->pstream, NULL, NULL);
+ pa_pdispatch_set_drain_callback(o->context->pdispatch, NULL, NULL);
+
+ if (pa_pdispatch_is_pending(o->context->pdispatch)) {
+ pa_pdispatch_set_drain_callback(o->context->pdispatch, pdispatch_drain_callback, o);
+ done = 0;
+ }
+
+ if (pa_pstream_is_pending(o->context->pstream)) {
+ pa_pstream_set_drain_callback(o->context->pstream, pstream_drain_callback, o);
+ done = 0;
+ }
+
+ if (!done)
+ pa_operation_ref(o);
+ else {
+ if (o->callback) {
+ void (*cb)(pa_context *c, void *userdata);
+ cb = (void (*)(pa_context*, void*)) o->callback;
+ cb(o->context, o->userdata);
+ }
+
+ pa_operation_done(o);
+ }
+
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_drain(pa_context *c, void (*cb) (pa_context*c, void *userdata), void *userdata) {
+ pa_operation *o;
+ assert(c && c->ref >= 1);
+
+ if (c->state != PA_CONTEXT_READY)
+ return NULL;
+
+ if (!pa_context_is_pending(c))
+ return NULL;
+
+ o = pa_operation_new(c, NULL);
+ assert(o);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ set_dispatch_callbacks(pa_operation_ref(o));
+
+ return o;
+}
+
+void pa_context_exit_daemon(pa_context *c) {
+ pa_tagstruct *t;
+ assert(c && c->ref >= 1);
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, PA_COMMAND_EXIT);
+ pa_tagstruct_putu32(t, c->ctag++);
+ pa_pstream_send_tagstruct(c->pstream, t);
+}
+
+void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *c, int _success, void *_userdata) = (void (*)(pa_context *c, int _success, void *_userdata)) o->callback;
+ cb(o->context, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, command);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_DEFAULT_SINK);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_set_default_source(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_DEFAULT_SOURCE);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+int pa_context_is_local(pa_context *c) {
+ assert(c);
+ return c->local;
+}
+
+pa_operation* pa_context_set_name(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && name && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_CLIENT_NAME);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+const char* pa_get_library_version(void) {
+ return PACKAGE_VERSION;
+}
+
+const char* pa_context_get_server(pa_context *c) {
+
+ if (!c->server)
+ return NULL;
+
+ if (*c->server == '{') {
+ char *e = strchr(c->server+1, '}');
+ return e ? e+1 : c->server;
+ }
+
+ return c->server;
+}
diff --git a/src/polyp/polyplib-context.h b/src/polyp/polyplib-context.h
new file mode 100644
index 00000000..febb75f4
--- /dev/null
+++ b/src/polyp/polyplib-context.h
@@ -0,0 +1,117 @@
+#ifndef foopolyplibcontexthfoo
+#define foopolyplibcontexthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/sample.h>
+#include <polyp/polyplib-def.h>
+#include <polyp/mainloop-api.h>
+#include <polyp/cdecl.h>
+#include <polyp/polyplib-operation.h>
+
+/** \file
+ * Connection contexts for asynchrononous communication with a
+ * server. A pa_context object wraps a connection to a polypaudio
+ * server using its native protocol. A context may be used to issue
+ * commands on the server or to create playback or recording
+ * streams. Multiple playback streams may be piped through a single
+ * connection context. Operations on the contect involving
+ * communication with the server are executed asynchronously: i.e. the
+ * client function do not implicitely wait for completion of the
+ * operation on the server. Instead the caller specifies a call back
+ * function that is called when the operation is completed. Currently
+ * running operations may be canceled using pa_operation_cancel(). */
+
+/** \example pacat.c
+ * A playback and recording tool using the asynchronous API */
+
+/** \example paplay.c
+ * A sound file playback tool using the asynchronous API, based on libsndfile */
+
+PA_C_DECL_BEGIN
+
+/** \pa_context
+ * An opaque connection context to a daemon */
+typedef struct pa_context pa_context;
+
+/** Instantiate a new connection context with an abstract mainloop API
+ * and an application name */
+pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name);
+
+/** Decrease the reference counter of the context by one */
+void pa_context_unref(pa_context *c);
+
+/** Increase the reference counter of the context by one */
+pa_context* pa_context_ref(pa_context *c);
+
+typedef void (*pa_context_state_callback)(pa_context *c, void *userdata);
+
+/** Set a callback function that is called whenever the context status changes */
+void pa_context_set_state_callback(pa_context *c, pa_context_state_callback callback, void *userdata);
+
+/** Return the error number of the last failed operation */
+int pa_context_errno(pa_context *c);
+
+/** Return non-zero if some data is pending to be written to the connection */
+int pa_context_is_pending(pa_context *c);
+
+/** Return the current context status */
+pa_context_state_t pa_context_get_state(pa_context *c);
+
+/** Connect the context to the specified server. If server is NULL,
+connect to the default server. This routine may but will not always
+return synchronously on error. Use pa_context_set_state_callback() to
+be notified when the connection is established. If spawn is non-zero
+and no specific server is specified or accessible a new daemon is
+spawned. If api is non-NULL, the functions specified in the structure
+are used when forking a new child process. */
+int pa_context_connect(pa_context *c, const char *server, int spawn, const pa_spawn_api *api);
+
+/** Terminate the context connection immediately */
+void pa_context_disconnect(pa_context *c);
+
+/** Drain the context. If there is nothing to drain, the function returns NULL */
+pa_operation* pa_context_drain(pa_context *c, void (*cb) (pa_context*c, void *userdata), void *userdata);
+
+/** Tell the daemon to exit. No operation object is returned as the
+ * connection is terminated when the daemon quits, thus this operation
+ * would never complete. */
+void pa_context_exit_daemon(pa_context *c);
+
+/** Set the name of the default sink. \since 0.4 */
+pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata);
+
+/** Set the name of the default source. \since 0.4 */
+pa_operation* pa_context_set_default_source(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata);
+
+/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. \since 0.5 */
+int pa_context_is_local(pa_context *c);
+
+/** Set a different application name for context on the server. \since 0.5 */
+pa_operation* pa_context_set_name(pa_context *c, const char *name, void(*cb)(pa_context*c, int success, void *userdata), void *userdata);
+
+/** Return the server name this context is connected to. \since 0.7 */
+const char* pa_context_get_server(pa_context *c);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-def.h b/src/polyp/polyplib-def.h
new file mode 100644
index 00000000..0591ce6c
--- /dev/null
+++ b/src/polyp/polyplib-def.h
@@ -0,0 +1,213 @@
+#ifndef foopolyplibdefhfoo
+#define foopolyplibdefhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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.
+
+ polypaudio 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 polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <polyp/cdecl.h>
+#include <polyp/sample.h>
+
+/** \file
+ * Global definitions */
+
+PA_C_DECL_BEGIN
+
+/** The state of a connection context */
+typedef enum pa_context_state {
+ PA_CONTEXT_UNCONNECTED, /**< The context hasn't been connected yet */
+ PA_CONTEXT_CONNECTING, /**< A connection is being established */
+ PA_CONTEXT_AUTHORIZING, /**< The client is authorizing itself to the daemon */
+ PA_CONTEXT_SETTING_NAME, /**< The client is passing its application name to the daemon */
+ PA_CONTEXT_READY, /**< The connection is established, the context is ready to execute operations */
+ PA_CONTEXT_FAILED, /**< The connection failed or was disconnected */
+ PA_CONTEXT_TERMINATED /**< The connection was terminated cleanly */
+} pa_context_state_t;
+
+/** The state of a stream */
+typedef enum pa_stream_state {
+ PA_STREAM_DISCONNECTED, /**< The stream is not yet connected to any sink or source */
+ PA_STREAM_CREATING, /**< The stream is being created */
+ PA_STREAM_READY, /**< The stream is established, you may pass audio data to it now */
+ PA_STREAM_FAILED, /**< An error occured that made the stream invalid */
+ PA_STREAM_TERMINATED /**< The stream has been terminated cleanly */
+} pa_stream_state_t;
+
+/** The state of an operation */
+typedef enum pa_operation_state {
+ PA_OPERATION_RUNNING, /**< The operation is still running */
+ PA_OPERATION_DONE, /**< The operation has been completed */
+ PA_OPERATION_CANCELED /**< The operation has been canceled */
+} pa_operation_state_t;
+
+/** An invalid index */
+#define PA_INVALID_INDEX ((uint32_t) -1)
+
+/** The direction of a pa_stream object */
+typedef enum pa_stream_direction {
+ PA_STREAM_NODIRECTION, /**< Invalid direction */
+ PA_STREAM_PLAYBACK, /**< Playback stream */
+ PA_STREAM_RECORD, /**< Record stream */
+ PA_STREAM_UPLOAD /**< Sample upload stream */
+} pa_stream_direction_t;
+
+/** Some special flags for stream connections. \since 0.6 */
+typedef enum pa_stream_flags {
+ PA_STREAM_START_CORKED = 1, /**< Create the stream corked, requiring an explicit pa_stream_cork() call to uncork it. */
+ PA_STREAM_INTERPOLATE_LATENCY = 2 /**< Interpolate the latency for
+ * this stream. When enabled,
+ * you can use
+ * pa_stream_interpolated_xxx()
+ * for synchronization. Using
+ * these functions instead of
+ * pa_stream_get_latency() has
+ * the advantage of not
+ * requiring a whole roundtrip
+ * for responses. Consider using
+ * this option when frequently
+ * requesting latency
+ * information. This is
+ * especially useful on long latency
+ * network connections. */
+} pa_stream_flags_t;
+
+/** Playback and record buffer metrics */
+typedef struct pa_buffer_attr {
+ uint32_t maxlength; /**< Maximum length of the buffer */
+ uint32_t tlength; /**< Playback only: target length of the buffer. The server tries to assure that at least tlength bytes are always available in the buffer */
+ uint32_t prebuf; /**< Playback only: pre-buffering. The server does not start with playback before at least prebug bytes are available in the buffer */
+ uint32_t minreq; /**< Playback only: minimum request. The server does not request less than minreq bytes from the client, instead waints until the buffer is free enough to request more bytes at once */
+ uint32_t fragsize; /**< Recording only: fragment size. The server sends data in blocks of fragsize bytes size. Large values deminish interactivity with other operations on the connection context but decrease control overhead. */
+} pa_buffer_attr;
+
+/** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */
+enum {
+ PA_ERROR_OK, /**< No error */
+ PA_ERROR_ACCESS, /**< Access failure */
+ PA_ERROR_COMMAND, /**< Unknown command */
+ PA_ERROR_INVALID, /**< Invalid argument */
+ PA_ERROR_EXIST, /**< Entity exists */
+ PA_ERROR_NOENTITY, /**< No such entity */
+ PA_ERROR_CONNECTIONREFUSED, /**< Connection refused */
+ PA_ERROR_PROTOCOL, /**< Protocol error */
+ PA_ERROR_TIMEOUT, /**< Timeout */
+ PA_ERROR_AUTHKEY, /**< No authorization key */
+ PA_ERROR_INTERNAL, /**< Internal error */
+ PA_ERROR_CONNECTIONTERMINATED, /**< Connection terminated */
+ PA_ERROR_KILLED, /**< Entity killed */
+ PA_ERROR_INVALIDSERVER, /**< Invalid server */
+ PA_ERROR_INITFAILED, /**< Module initialization failed */
+ PA_ERROR_MAX /**< Not really an error but the first invalid error code */
+};
+
+/** Subscription event mask, as used by pa_context_subscribe() */
+typedef enum pa_subscription_mask {
+ PA_SUBSCRIPTION_MASK_NULL = 0, /**< No events */
+ PA_SUBSCRIPTION_MASK_SINK = 1, /**< Sink events */
+ PA_SUBSCRIPTION_MASK_SOURCE = 2, /**< Source events */
+ PA_SUBSCRIPTION_MASK_SINK_INPUT = 4, /**< Sink input events */
+ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 8, /**< Source output events */
+ PA_SUBSCRIPTION_MASK_MODULE = 16, /**< Module events */
+ PA_SUBSCRIPTION_MASK_CLIENT = 32, /**< Client events */
+ PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 64, /**< Sample cache events */
+ PA_SUBSCRIPTION_MASK_SERVER = 128, /**< Other global server changes. \since 0.4 */
+ PA_SUBSCRIPTION_MASK_AUTOLOAD = 256 /**< Autoload table events. \since 0.5 */
+} pa_subscription_mask_t;
+
+/** Subscription event types, as used by pa_context_subscribe() */
+typedef enum pa_subscription_event_type {
+ PA_SUBSCRIPTION_EVENT_SINK = 0, /**< Event type: Sink */
+ PA_SUBSCRIPTION_EVENT_SOURCE = 1, /**< Event type: Source */
+ PA_SUBSCRIPTION_EVENT_SINK_INPUT = 2, /**< Event type: Sink input */
+ PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 3, /**< Event type: Source output */
+ PA_SUBSCRIPTION_EVENT_MODULE = 4, /**< Event type: Module */
+ PA_SUBSCRIPTION_EVENT_CLIENT = 5, /**< Event type: Client */
+ PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 6, /**< Event type: Sample cache item */
+ PA_SUBSCRIPTION_EVENT_SERVER = 7, /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. \since 0.4 */
+ PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8, /**< Event type: Autoload table changes. \since 0.5 */
+ PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 15, /**< A mask to extract the event type from an event value */
+
+ PA_SUBSCRIPTION_EVENT_NEW = 0, /**< A new object was created */
+ PA_SUBSCRIPTION_EVENT_CHANGE = 16, /**< A property of the object was modified */
+ PA_SUBSCRIPTION_EVENT_REMOVE = 32, /**< An object was removed */
+ PA_SUBSCRIPTION_EVENT_TYPE_MASK = 16+32 /**< A mask to extract the event operation from an event value */
+} pa_subscription_event_type_t;
+
+/** Return one if an event type t matches an event mask bitfield */
+#define pa_subscription_match_flags(m, t) (!!((m) & (1 << ((t) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK))))
+
+/** A structure for latency info. See pa_stream_get_latency(). The
+ * total output latency a sample that is written with
+ * pa_stream_write() takes to be played may be estimated by
+ * sink_usec+buffer_usec+transport_usec. The output buffer to which
+ * buffer_usec relates may be manipulated freely (with
+ * pa_stream_write()'s delta argument, pa_stream_flush() and friends),
+ * the buffers sink_usec/source_usec relates to is a first-in
+ * first-out buffer which cannot be flushed or manipulated in any
+ * way. The total input latency a sample that is recorded takes to be
+ * delivered to the application is:
+ * source_usec+buffer_usec+transport_usec-sink_usec. (Take care of
+ * sign issues!) When connected to a monitor source sink_usec contains
+ * the latency of the owning sink.*/
+typedef struct pa_latency_info {
+ pa_usec_t buffer_usec; /**< Time in usecs the current buffer takes to play. For both playback and record streams. */
+ pa_usec_t sink_usec; /**< Time in usecs a sample takes to be played on the sink. For playback streams and record streams connected to a monitor source. */
+ pa_usec_t source_usec; /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. \since 0.5*/
+ pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. \since 0.5 */
+ int playing; /**< Non-zero when the stream is currently playing. Only for playback streams. */
+ uint32_t queue_length; /**< Queue size in bytes. For both playback and record streams. */
+ int synchronized_clocks; /**< Non-zero if the local and the
+ * remote machine have synchronized
+ * clocks. If synchronized clocks are
+ * detected transport_usec becomes much
+ * more reliable. However, the code that
+ * detects synchronized clocks is very
+ * limited und unreliable itself. \since
+ * 0.5 */
+ struct timeval timestamp; /**< The time when this latency info was current */
+ uint64_t counter; /**< The byte counter current when the latency info was requested. \since 0.6 */
+} pa_latency_info;
+
+/** A structure for the spawn api. This may be used to integrate auto
+ * spawned daemons into your application. For more information see
+ * pa_context_connect(). When spawning a new child process the
+ * waitpid() is used on the child's PID. The spawn routine will not
+ * block or ignore SIGCHLD signals, since this cannot be done in a
+ * thread compatible way. You might have to do this in
+ * prefork/postfork. \since 0.4 */
+typedef struct pa_spawn_api {
+ void (*prefork)(void); /**< Is called just before the fork in the parent process. May be NULL. */
+ void (*postfork)(void); /**< Is called immediately after the fork in the parent process. May be NULL.*/
+ void (*atfork)(void); /**< Is called immediately after the
+ * fork in the child process. May be
+ * NULL. It is not safe to close all
+ * file descriptors in this function
+ * unconditionally, since a UNIX socket
+ * (created using socketpair()) is
+ * passed to the new process. */
+} pa_spawn_api;
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-error.c b/src/polyp/polyplib-error.c
new file mode 100644
index 00000000..188d6a93
--- /dev/null
+++ b/src/polyp/polyplib-error.c
@@ -0,0 +1,54 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "polyplib-error.h"
+#include <polypcore/native-common.h>
+
+static const char* const errortab[PA_ERROR_MAX] = {
+ [PA_ERROR_OK] = "OK",
+ [PA_ERROR_ACCESS] = "Access denied",
+ [PA_ERROR_COMMAND] = "Unknown command",
+ [PA_ERROR_INVALID] = "Invalid argument",
+ [PA_ERROR_EXIST] = "Entity exists",
+ [PA_ERROR_NOENTITY] = "No such entity",
+ [PA_ERROR_CONNECTIONREFUSED] = "Connection refused",
+ [PA_ERROR_PROTOCOL] = "Protocol error",
+ [PA_ERROR_TIMEOUT] = "Timeout",
+ [PA_ERROR_AUTHKEY] = "No authorization key",
+ [PA_ERROR_INTERNAL] = "Internal error",
+ [PA_ERROR_CONNECTIONTERMINATED] = "Connection terminated",
+ [PA_ERROR_KILLED] = "Entity killed",
+ [PA_ERROR_INVALIDSERVER] = "Invalid server",
+};
+
+const char*pa_strerror(uint32_t error) {
+ if (error >= PA_ERROR_MAX)
+ return NULL;
+
+ return errortab[error];
+}
diff --git a/src/polyp/polyplib-error.h b/src/polyp/polyplib-error.h
new file mode 100644
index 00000000..1bb97822
--- /dev/null
+++ b/src/polyp/polyplib-error.h
@@ -0,0 +1,38 @@
+#ifndef foopolypliberrorhfoo
+#define foopolypliberrorhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <polyp/cdecl.h>
+
+/** \file
+ * Error management */
+
+PA_C_DECL_BEGIN
+
+/** Return a human readable error message for the specified numeric error code */
+const char* pa_strerror(uint32_t error);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-internal.h b/src/polyp/polyplib-internal.h
new file mode 100644
index 00000000..b95a20f3
--- /dev/null
+++ b/src/polyp/polyplib-internal.h
@@ -0,0 +1,154 @@
+#ifndef foopolyplibinternalhfoo
+#define foopolyplibinternalhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/mainloop-api.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/pstream.h>
+#include <polypcore/pdispatch.h>
+#include <polypcore/dynarray.h>
+
+#include "polyplib-context.h"
+#include "polyplib-stream.h"
+#include "polyplib-operation.h"
+#include <polypcore/llist.h>
+#include <polypcore/native-common.h>
+#include <polyp/client-conf.h>
+#include <polypcore/strlist.h>
+#include <polypcore/mcalign.h>
+
+#define DEFAULT_TIMEOUT (10)
+
+struct pa_context {
+ int ref;
+
+ char *name;
+ pa_mainloop_api* mainloop;
+
+ pa_socket_client *client;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+
+ pa_dynarray *record_streams, *playback_streams;
+ PA_LLIST_HEAD(pa_stream, streams);
+ PA_LLIST_HEAD(pa_operation, operations);
+
+ uint32_t ctag;
+ uint32_t error;
+ pa_context_state_t state;
+
+ void (*state_callback)(pa_context*c, void *userdata);
+ void *state_userdata;
+
+ void (*subscribe_callback)(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata);
+ void *subscribe_userdata;
+
+ pa_memblock_stat *memblock_stat;
+
+ int local;
+ int do_autospawn;
+ int autospawn_lock_fd;
+ pa_spawn_api spawn_api;
+
+ pa_strlist *server_list;
+
+ char *server;
+
+ pa_client_conf *conf;
+};
+
+struct pa_stream {
+ int ref;
+ pa_context *context;
+ pa_mainloop_api *mainloop;
+ PA_LLIST_FIELDS(pa_stream);
+
+ char *name;
+ pa_buffer_attr buffer_attr;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ uint32_t channel;
+ int channel_valid;
+ uint32_t device_index;
+ pa_stream_direction_t direction;
+ uint32_t requested_bytes;
+ uint64_t counter;
+ pa_usec_t previous_time;
+ pa_usec_t previous_ipol_time;
+ pa_stream_state_t state;
+ pa_mcalign *mcalign;
+
+ int interpolate;
+ int corked;
+
+ uint32_t ipol_usec;
+ struct timeval ipol_timestamp;
+ pa_time_event *ipol_event;
+ int ipol_requested;
+
+ void (*state_callback)(pa_stream*c, void *userdata);
+ void *state_userdata;
+
+ void (*read_callback)(pa_stream *p, const void*data, size_t length, void *userdata);
+ void *read_userdata;
+
+ void (*write_callback)(pa_stream *p, size_t length, void *userdata);
+ void *write_userdata;
+};
+
+typedef void (*pa_operation_callback)(void);
+
+struct pa_operation {
+ int ref;
+ pa_context *context;
+ pa_stream *stream;
+ PA_LLIST_FIELDS(pa_operation);
+
+ pa_operation_state_t state;
+ void *userdata;
+ pa_operation_callback callback;
+};
+
+void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+pa_operation *pa_operation_new(pa_context *c, pa_stream *s);
+void pa_operation_done(pa_operation *o);
+
+void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+void pa_context_fail(pa_context *c, int error);
+void pa_context_set_state(pa_context *c, pa_context_state_t st);
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t);
+pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata);
+
+void pa_stream_set_state(pa_stream *s, pa_stream_state_t st);
+
+void pa_stream_trash_ipol(pa_stream *s);
+
+
+#endif
diff --git a/src/polyp/polyplib-introspect.c b/src/polyp/polyplib-introspect.c
new file mode 100644
index 00000000..0bdffa35
--- /dev/null
+++ b/src/polyp/polyplib-introspect.c
@@ -0,0 +1,1003 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include "polyplib-introspect.h"
+#include "polyplib-context.h"
+#include "polyplib-internal.h"
+#include <polypcore/pstream-util.h>
+#include <polypcore/gccmacro.h>
+
+/*** Statistics ***/
+
+static void context_stat_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ pa_stat_info i, *p = &i;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ p = NULL;
+ } else if (pa_tagstruct_getu32(t, &i.memblock_total) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_total_size) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_allocated) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_allocated_size) < 0 ||
+ pa_tagstruct_getu32(t, &i.scache_size) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_stat_info*_i, void *_userdata) = (void (*)(pa_context *s, const pa_stat_info*_i, void *_userdata)) o->callback;
+ cb(o->context, p, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_stat(pa_context *c, void (*cb)(pa_context *c, const pa_stat_info*i, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_STAT, context_stat_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Server Info ***/
+
+static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ pa_server_info i, *p = &i;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ p = NULL;
+ } else if (pa_tagstruct_gets(t, &i.server_name) < 0 ||
+ pa_tagstruct_gets(t, &i.server_version) < 0 ||
+ pa_tagstruct_gets(t, &i.user_name) < 0 ||
+ pa_tagstruct_gets(t, &i.host_name) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_gets(t, &i.default_sink_name) < 0 ||
+ pa_tagstruct_gets(t, &i.default_source_name) < 0 ||
+ pa_tagstruct_getu32(t, &i.cookie) < 0 ||
+ !pa_tagstruct_eof(t)) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_server_info*_i, void *_userdata) = (void (*)(pa_context *s, const pa_server_info*_i, void *_userdata)) o->callback;
+ cb(o->context, p, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_server_info(pa_context *c, void (*cb)(pa_context *c, const pa_server_info*i, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SERVER_INFO, context_get_server_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Sink Info ***/
+
+static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sink_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_getu32(t, &i.monitor_source) < 0 ||
+ pa_tagstruct_gets(t, &i.monitor_source_name) < 0 ||
+ pa_tagstruct_get_usec(t, &i.latency) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sink_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sink_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sink_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sink_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sink_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INFO_LIST, context_get_sink_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+/*** Source info ***/
+
+static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_source_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 ||
+ pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 ||
+ pa_tagstruct_get_usec(t, &i.latency) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_source_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_source_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_source_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_source_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_source_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_INFO_LIST, context_get_source_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+/*** Client info ***/
+
+static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_client_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0 ) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_client_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_client_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_client_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_client_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_client_info*i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_CLIENT_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_client_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_client_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_client_info*i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_CLIENT_INFO_LIST, context_get_client_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Module info ***/
+
+static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_module_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.argument) < 0 ||
+ pa_tagstruct_getu32(t, &i.n_used) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.auto_unload) < 0) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_module_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_module_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_module_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_module_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_module_info*i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_MODULE_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_module_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_module_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_module_info*i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_MODULE_INFO_LIST, context_get_module_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Sink input info ***/
+
+static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sink_input_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &i.client) < 0 ||
+ pa_tagstruct_getu32(t, &i.sink) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.sink_usec) < 0 ||
+ pa_tagstruct_gets(t, &i.resample_method) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sink_input_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sink_input_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sink_input_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sink_input_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INPUT_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_input_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_sink_input_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INPUT_INFO_LIST, context_get_sink_input_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Source output info ***/
+
+static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_source_output_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &i.client) < 0 ||
+ pa_tagstruct_getu32(t, &i.source) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.source_usec) < 0 ||
+ pa_tagstruct_gets(t, &i.resample_method) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_source_output_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_source_output_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_source_output_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_source_output_info*_i, int _eof, void *_userdata))o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_source_output_info*i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_OUTPUT_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_output_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_source_output_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_source_output_info*i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, context_get_source_output_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+/*** Volume manipulation ***/
+
+pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && idx != PA_INVALID_INDEX);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_VOLUME);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_VOLUME);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && idx != PA_INVALID_INDEX);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+/** Sample Cache **/
+
+static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sample_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_usec(t, &i.duration) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.bytes) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.lazy) < 0 ||
+ pa_tagstruct_gets(t, &i.filename) < 0) {
+
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sample_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sample_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_sample_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_sample_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb && name);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SAMPLE_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SAMPLE_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_sample_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SAMPLE_INFO_LIST, context_get_sample_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && idx != PA_INVALID_INDEX);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, command);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_CLIENT, idx, cb, userdata);
+}
+
+pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_SINK_INPUT, idx, cb, userdata);
+}
+
+pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_SOURCE_OUTPUT, idx, cb, userdata);
+}
+
+static void load_module_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ uint32_t idx = -1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ } else if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *c, uint32_t _idx, void *_userdata) = (void (*)(pa_context *c, uint32_t _idx, void *_userdata)) o->callback;
+ cb(o->context, idx, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, void (*cb)(pa_context *c, uint32_t idx, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name && argument);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_LOAD_MODULE);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_puts(t, argument);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, load_module_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ return command_kill(c, PA_COMMAND_UNLOAD_MODULE, idx, cb, userdata);
+}
+
+/*** Autoload stuff ***/
+
+static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eof = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eof = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_autoload_info i;
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.type) < 0 ||
+ pa_tagstruct_gets(t, &i.module) < 0 ||
+ pa_tagstruct_gets(t, &i.argument) < 0) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_autoload_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_autoload_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, const pa_autoload_info*_i, int _eof, void *_userdata) = (void (*)(pa_context *s, const pa_autoload_info*_i, int _eof, void *_userdata)) o->callback;
+ cb(o->context, NULL, eof, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb && name);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_AUTOLOAD_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(c && cb && idx != PA_INVALID_INDEX);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_AUTOLOAD_INFO);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_get_autoload_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_AUTOLOAD_INFO_LIST, context_get_autoload_info_callback, (pa_operation_callback) cb, userdata);
+}
+
+static void context_add_autoload_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ uint32_t idx;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ idx = PA_INVALID_INDEX;
+ } else if (pa_tagstruct_getu32(t, &idx) ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_context *s, uint32_t _idx, void *_userdata) = (void (*)(pa_context *s, uint32_t _idx, void *_userdata)) o->callback;
+ cb(o->context, idx, o->userdata);
+ }
+
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, void (*cb)(pa_context *c, int success, void *userdata), void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name && module && argument);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_ADD_AUTOLOAD);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_tagstruct_puts(t, module);
+ pa_tagstruct_puts(t, argument);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_add_autoload_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, void (*cb)(pa_context *c, int success, void *userdata), void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_REMOVE_AUTOLOAD);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && idx != PA_INVALID_INDEX);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_REMOVE_AUTOLOAD);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
diff --git a/src/polyp/polyplib-introspect.h b/src/polyp/polyplib-introspect.h
new file mode 100644
index 00000000..d3489908
--- /dev/null
+++ b/src/polyp/polyplib-introspect.h
@@ -0,0 +1,279 @@
+#ifndef foopolyplibintrospecthfoo
+#define foopolyplibintrospecthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+#include <polyp/polyplib-operation.h>
+#include <polyp/polyplib-context.h>
+#include <polyp/cdecl.h>
+#include <polyp/channelmap.h>
+#include <polyp/volume.h>
+
+/** \file
+ *
+ * Routines for daemon introspection. When enumerating all entitites
+ * of a certain kind, use the pa_context_xxx_list() functions. The
+ * specified callback function is called once for each entry. The
+ * enumeration is finished by a call to the callback function with
+ * is_last=1 and i=NULL. Strings referenced in pa_xxx_info structures
+ * and the structures themselves point to internal memory that may not
+ * be modified. That memory is only valid during the call to the
+ * callback function. A deep copy is required if you need this data
+ * outside the callback functions. An error is signalled by a call to * the callback function with i=NULL and is_last=0.
+ *
+ * When using the routines that ask fo a single entry only, a callback
+ * with the same signature is used. However, no finishing call to the
+ * routine is issued. */
+
+PA_C_DECL_BEGIN
+
+/** Stores information about sinks */
+typedef struct pa_sink_info {
+ const char *name; /**< Name of the sink */
+ uint32_t index; /**< Index of the sink */
+ const char *description; /**< Description of this sink */
+ pa_sample_spec sample_spec; /**< Sample spec of this sink */
+ pa_channel_map channel_map; /**< Channel map \since 0.9 */
+ uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */
+ pa_cvolume volume; /**< Volume of the sink */
+ uint32_t monitor_source; /**< Index of the monitor source connected to this sink */
+ const char *monitor_source_name; /**< The name of the monitor source */
+ pa_usec_t latency; /**< Length of filled playback buffer of this sink */
+ const char *driver; /**< Driver name. \since 0.9 */
+} pa_sink_info;
+
+/** Get information about a sink by its name */
+pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get information about a sink by its index */
+pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t id, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete sink list */
+pa_operation* pa_context_get_sink_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_info *i, int is_last, void *userdata), void *userdata);
+
+/** Stores information about sources */
+typedef struct pa_source_info {
+ const char *name ; /**< Name of the source */
+ uint32_t index; /**< Index of the source */
+ const char *description; /**< Description of this source */
+ pa_sample_spec sample_spec; /**< Sample spec of this source */
+ pa_channel_map channel_map; /**< Channel map \since 0.9 */
+ uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX */
+ uint32_t monitor_of_sink; /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */
+ const char *monitor_of_sink_name; /**< Name of the owning sink, or PA_INVALID_INDEX */
+ pa_usec_t latency; /**< Length of filled record buffer of this source. \since 0.5 */
+ const char *driver; /**< Driver name \since 0.9 */
+} pa_source_info;
+
+/** Get information about a source by its name */
+pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get information about a source by its index */
+pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t id, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete source list */
+pa_operation* pa_context_get_source_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_source_info *i, int is_last, void *userdata), void *userdata);
+
+/** Server information */
+typedef struct pa_server_info {
+ const char *user_name; /**< User name of the daemon process */
+ const char *host_name; /**< Host name the daemon is running on */
+ const char *server_version; /**< Version string of the daemon */
+ const char *server_name; /**< Server package name (usually "polypaudio") */
+ pa_sample_spec sample_spec; /**< Default sample specification */
+ const char *default_sink_name; /**< Name of default sink. \since 0.4 */
+ const char *default_source_name; /**< Name of default sink. \since 0.4*/
+ uint32_t cookie; /**< A random cookie for identifying this instance of polypaudio. \since 0.8 */
+} pa_server_info;
+
+/** Get some information about the server */
+pa_operation* pa_context_get_server_info(pa_context *c, void (*cb)(pa_context *c, const pa_server_info*i, void *userdata), void *userdata);
+
+/** Stores information about modules */
+typedef struct pa_module_info {
+ uint32_t index; /**< Index of the module */
+ const char*name, /**< Name of the module */
+ *argument; /**< Argument string of the module */
+ uint32_t n_used; /**< Usage counter or PA_INVALID_INDEX */
+ int auto_unload; /**< Non-zero if this is an autoloaded module */
+} pa_module_info;
+
+/** Get some information about a module by its index */
+pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_module_info*i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete list of currently loaded modules */
+pa_operation* pa_context_get_module_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_module_info*i, int is_last, void *userdata), void *userdata);
+
+/** Stores information about clients */
+typedef struct pa_client_info {
+ uint32_t index; /**< Index of this client */
+ const char *name; /**< Name of this client */
+ uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX */
+ const char *driver; /**< Driver name \since 0.9 */
+} pa_client_info;
+
+/** Get information about a client by its index */
+pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_client_info*i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete client list */
+pa_operation* pa_context_get_client_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_client_info*i, int is_last, void *userdata), void *userdata);
+
+/** Stores information about sink inputs */
+typedef struct pa_sink_input_info {
+ uint32_t index; /**< Index of the sink input */
+ const char *name; /**< Name of the sink input */
+ uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */
+ uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
+ uint32_t sink; /**< Index of the connected sink */
+ pa_sample_spec sample_spec; /**< The sample specification of the sink input */
+ pa_channel_map channel_map; /**< Channel map */
+ pa_cvolume volume; /**< The volume of this sink input */
+ pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_latency_info for details */
+ pa_usec_t sink_usec; /**< Latency of the sink device, see pa_latency_info for details */
+ const char *resample_method; /**< Thre resampling method used by this sink input. \since 0.7 */
+ const char *driver; /**< Driver name \since 0.9 */
+} pa_sink_input_info;
+
+/** Get some information about a sink input by its index */
+pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete sink input list */
+pa_operation* pa_context_get_sink_input_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata);
+
+/** Stores information about source outputs */
+typedef struct pa_source_output_info {
+ uint32_t index; /**< Index of the sink input */
+ const char *name; /**< Name of the sink input */
+ uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */
+ uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
+ uint32_t source; /**< Index of the connected source */
+ pa_sample_spec sample_spec; /**< The sample specification of the source output */
+ pa_channel_map channel_map; /**< Channel map */
+ pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_latency_info for details. \since 0.5 */
+ pa_usec_t source_usec; /**< Latency of the source device, see pa_latency_info for details. \since 0.5 */
+ const char *resample_method; /**< Thre resampling method used by this source output. \since 0.7 */
+ const char *driver; /**< Driver name \since 0.9 */
+} pa_source_output_info;
+
+/** Get information about a source output by its index */
+pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_source_output_info*i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete list of source outputs */
+pa_operation* pa_context_get_source_output_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_source_output_info*i, int is_last, void *userdata), void *userdata);
+
+/** Set the volume of a sink device specified by its index */
+pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Set the volume of a sink device specified by its name */
+pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Set the volume of a sink input stream */
+pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Memory block statistics */
+typedef struct pa_stat_info {
+ uint32_t memblock_total; /**< Currently allocated memory blocks */
+ uint32_t memblock_total_size; /**< Currentl total size of allocated memory blocks */
+ uint32_t memblock_allocated; /**< Allocated memory blocks during the whole lifetime of the daemon */
+ uint32_t memblock_allocated_size; /**< Total size of all memory blocks allocated during the whole lifetime of the daemon */
+ uint32_t scache_size; /**< Total size of all sample cache entries. \since 0.4 */
+} pa_stat_info;
+
+/** Get daemon memory block statistics */
+pa_operation* pa_context_stat(pa_context *c, void (*cb)(pa_context *c, const pa_stat_info *i, void *userdata), void *userdata);
+
+/** Stores information about sample cache entries */
+typedef struct pa_sample_info {
+ uint32_t index; /**< Index of this entry */
+ const char *name; /**< Name of this entry */
+ pa_cvolume volume; /**< Default volume of this entry */
+ pa_sample_spec sample_spec; /**< Sample specification of the sample */
+ pa_channel_map channel_map; /**< The channel map */
+ pa_usec_t duration; /**< Duration of this entry */
+ uint32_t bytes; /**< Length of this sample in bytes. \since 0.4 */
+ int lazy; /**< Non-zero when this is a lazy cache entry. \since 0.5 */
+ const char *filename; /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. \since 0.5 */
+} pa_sample_info;
+
+/** Get information about a sample by its name */
+pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get information about a sample by its index */
+pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete list of samples stored in the daemon. */
+pa_operation* pa_context_get_sample_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sample_info *i, int is_last, void *userdata), void *userdata);
+
+/** Kill a client. \since 0.5 */
+pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Kill a sink input. \since 0.5 */
+pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Kill a source output. \since 0.5 */
+pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Load a module. \since 0.5 */
+pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, void (*cb)(pa_context *c, uint32_t idx, void *userdata), void *userdata);
+
+/** Unload a module. \since 0.5 */
+pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Type of an autoload entry. \since 0.5 */
+typedef enum pa_autoload_type {
+ PA_AUTOLOAD_SINK = 0,
+ PA_AUTOLOAD_SOURCE = 1
+} pa_autoload_type_t;
+
+/** Stores information about autoload entries. \since 0.5 */
+typedef struct pa_autoload_info {
+ uint32_t index; /**< Index of this autoload entry */
+ const char *name; /**< Name of the sink or source */
+ pa_autoload_type_t type; /**< Type of the autoload entry */
+ const char *module; /**< Module name to load */
+ const char *argument; /**< Argument string for module */
+} pa_autoload_info;
+
+/** Get info about a specific autoload entry. \since 0.6 */
+pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get info about a specific autoload entry. \since 0.6 */
+pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata);
+
+/** Get the complete list of autoload entries. \since 0.5 */
+pa_operation* pa_context_get_autoload_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata), void *userdata);
+
+/** Add a new autoload entry. \since 0.5 */
+pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, void (*cb)(pa_context *c, int idx, void *userdata), void* userdata);
+
+/** Remove an autoload entry. \since 0.6 */
+pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, void (*cb)(pa_context *c, int success, void *userdata), void* userdata);
+
+/** Remove an autoload entry. \since 0.6 */
+pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, void (*cb)(pa_context *c, int success, void *userdata), void* userdata);
+
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-operation.c b/src/polyp/polyplib-operation.c
new file mode 100644
index 00000000..ea336c17
--- /dev/null
+++ b/src/polyp/polyplib-operation.c
@@ -0,0 +1,103 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include <polypcore/xmalloc.h>
+#include "polyplib-internal.h"
+#include "polyplib-operation.h"
+
+pa_operation *pa_operation_new(pa_context *c, pa_stream *s) {
+ pa_operation *o;
+ assert(c);
+
+ o = pa_xmalloc(sizeof(pa_operation));
+ o->ref = 1;
+ o->context = pa_context_ref(c);
+ o->stream = s ? pa_stream_ref(s) : NULL;
+
+ o->state = PA_OPERATION_RUNNING;
+ o->userdata = NULL;
+ o->callback = NULL;
+
+ PA_LLIST_PREPEND(pa_operation, o->context->operations, o);
+ return pa_operation_ref(o);
+}
+
+pa_operation *pa_operation_ref(pa_operation *o) {
+ assert(o && o->ref >= 1);
+ o->ref++;
+ return o;
+}
+
+void pa_operation_unref(pa_operation *o) {
+ assert(o && o->ref >= 1);
+
+ if ((--(o->ref)) == 0) {
+ assert(!o->context);
+ assert(!o->stream);
+ free(o);
+ }
+}
+
+static void operation_set_state(pa_operation *o, pa_operation_state_t st) {
+ assert(o && o->ref >= 1);
+
+ if (st == o->state)
+ return;
+
+ if (!o->context)
+ return;
+
+ o->state = st;
+
+ if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) {
+ PA_LLIST_REMOVE(pa_operation, o->context->operations, o);
+ pa_context_unref(o->context);
+ if (o->stream)
+ pa_stream_unref(o->stream);
+ o->context = NULL;
+ o->stream = NULL;
+ o->callback = NULL;
+ o->userdata = NULL;
+
+ pa_operation_unref(o);
+ }
+}
+
+void pa_operation_cancel(pa_operation *o) {
+ assert(o && o->ref >= 1);
+ operation_set_state(o, PA_OPERATION_CANCELED);
+}
+
+void pa_operation_done(pa_operation *o) {
+ assert(o && o->ref >= 1);
+ operation_set_state(o, PA_OPERATION_DONE);
+}
+
+pa_operation_state_t pa_operation_get_state(pa_operation *o) {
+ assert(o && o->ref >= 1);
+ return o->state;
+}
diff --git a/src/polyp/polyplib-operation.h b/src/polyp/polyplib-operation.h
new file mode 100644
index 00000000..cac03e30
--- /dev/null
+++ b/src/polyp/polyplib-operation.h
@@ -0,0 +1,51 @@
+#ifndef foopolypliboperationhfoo
+#define foopolypliboperationhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/cdecl.h>
+#include <polyp/polyplib-def.h>
+
+/** \file
+ * Asynchronous operations */
+
+PA_C_DECL_BEGIN
+
+/** \pa_operation
+ * An asynchronous operation object */
+typedef struct pa_operation pa_operation;
+
+/** Increase the reference count by one */
+pa_operation *pa_operation_ref(pa_operation *o);
+
+/** Decrease the reference count by one */
+void pa_operation_unref(pa_operation *o);
+
+/** Cancel the operation. Beware! This will not necessarily cancel the execution of the operation on the server side. */
+void pa_operation_cancel(pa_operation *o);
+
+/** Return the current status of the operation */
+pa_operation_state_t pa_operation_get_state(pa_operation *o);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-scache.c b/src/polyp/polyplib-scache.c
new file mode 100644
index 00000000..1315af97
--- /dev/null
+++ b/src/polyp/polyplib-scache.c
@@ -0,0 +1,127 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "polyplib-scache.h"
+#include "polyplib-internal.h"
+#include <polypcore/pstream-util.h>
+
+void pa_stream_connect_upload(pa_stream *s, size_t length) {
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ assert(s && length);
+
+ pa_stream_ref(s);
+
+ s->state = PA_STREAM_CREATING;
+ s->direction = PA_STREAM_UPLOAD;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_CREATE_UPLOAD_STREAM);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_puts(t, s->name);
+ pa_tagstruct_put_sample_spec(t, &s->sample_spec);
+ pa_tagstruct_putu32(t, length);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s);
+
+ pa_stream_unref(s);
+}
+
+void pa_stream_finish_upload(pa_stream *s) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s);
+
+ if (!s->channel_valid || !s->context->state == PA_CONTEXT_READY)
+ return;
+
+ pa_stream_ref(s);
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+
+ pa_tagstruct_putu32(t, PA_COMMAND_FINISH_UPLOAD_STREAM);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s);
+
+ pa_stream_unref(s);
+}
+
+pa_operation * pa_context_play_sample(pa_context *c, const char *name, const char *dev, uint32_t volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name && *name && (!dev || *dev));
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ if (!dev)
+ dev = c->conf->default_sink;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAY_SAMPLE);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, (uint32_t) -1);
+ pa_tagstruct_puts(t, dev);
+ pa_tagstruct_putu32(t, volume);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_context_remove_sample(pa_context *c, const char *name, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c && name);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, PA_COMMAND_REMOVE_SAMPLE);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
diff --git a/src/polyp/polyplib-scache.h b/src/polyp/polyplib-scache.h
new file mode 100644
index 00000000..89d27597
--- /dev/null
+++ b/src/polyp/polyplib-scache.h
@@ -0,0 +1,50 @@
+#ifndef foopolyplibscachehfoo
+#define foopolyplibscachehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <polyp/polyplib-context.h>
+#include <polyp/polyplib-stream.h>
+#include <polyp/cdecl.h>
+
+/** \file
+ * All sample cache related routines */
+
+PA_C_DECL_BEGIN
+
+/** Make this stream a sample upload stream */
+void pa_stream_connect_upload(pa_stream *s, size_t length);
+
+/** Finish the sample upload, the stream name will become the sample name. You cancel a sample upload by issuing pa_stream_disconnect() */
+void pa_stream_finish_upload(pa_stream *s);
+
+/** Play a sample from the sample cache to the specified device. If the latter is NULL use the default sink. Returns an operation object */
+pa_operation* pa_context_play_sample(pa_context *c, const char *name, const char *dev, uint32_t volume, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */
+pa_operation* pa_context_remove_sample(pa_context *c, const char *name, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-simple.c b/src/polyp/polyplib-simple.c
new file mode 100644
index 00000000..7436f007
--- /dev/null
+++ b/src/polyp/polyplib-simple.c
@@ -0,0 +1,393 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "polyplib-simple.h"
+#include "polyplib.h"
+#include "mainloop.h"
+#include <polypcore/native-common.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+struct pa_simple {
+ pa_mainloop *mainloop;
+ pa_context *context;
+ pa_stream *stream;
+ pa_stream_direction_t direction;
+
+ int dead;
+
+ void *read_data;
+ size_t read_index, read_length;
+ pa_usec_t latency;
+};
+
+static void read_callback(pa_stream *s, const void*data, size_t length, void *userdata);
+
+static int check_error(pa_simple *p, int *rerror) {
+ pa_context_state_t cst;
+ pa_stream_state_t sst;
+ assert(p);
+
+ if ((cst = pa_context_get_state(p->context)) == PA_CONTEXT_FAILED)
+ goto fail;
+
+ assert(cst != PA_CONTEXT_TERMINATED);
+
+ if (p->stream) {
+ if ((sst = pa_stream_get_state(p->stream)) == PA_STREAM_FAILED)
+ goto fail;
+
+ assert(sst != PA_STREAM_TERMINATED);
+ }
+
+ return 0;
+
+fail:
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ p->dead = 1;
+
+ return -1;
+}
+
+static int iterate(pa_simple *p, int block, int *rerror) {
+ assert(p && p->context && p->mainloop);
+
+ if (check_error(p, rerror) < 0)
+ return -1;
+
+ if (!block && !pa_context_is_pending(p->context))
+ return 0;
+
+ do {
+ if (pa_mainloop_iterate(p->mainloop, 1, NULL) < 0) {
+ if (rerror)
+ *rerror = PA_ERROR_INTERNAL;
+ return -1;
+ }
+
+ if (check_error(p, rerror) < 0)
+ return -1;
+
+ } while (pa_context_is_pending(p->context));
+
+
+ while (pa_mainloop_deferred_pending(p->mainloop)) {
+
+ if (pa_mainloop_iterate(p->mainloop, 0, NULL) < 0) {
+ if (rerror)
+ *rerror = PA_ERROR_INTERNAL;
+ return -1;
+ }
+
+ if (check_error(p, rerror) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+pa_simple* pa_simple_new(
+ const char *server,
+ const char *name,
+ pa_stream_direction_t dir,
+ const char *dev,
+ const char *stream_name,
+ const pa_sample_spec *ss,
+ const pa_buffer_attr *attr,
+ int *rerror) {
+
+ pa_simple *p;
+ int error = PA_ERROR_INTERNAL;
+ assert(ss && (dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD));
+
+ p = pa_xmalloc(sizeof(pa_simple));
+ p->context = NULL;
+ p->stream = NULL;
+ p->mainloop = pa_mainloop_new();
+ assert(p->mainloop);
+ p->dead = 0;
+ p->direction = dir;
+ p->read_data = NULL;
+ p->read_index = p->read_length = 0;
+ p->latency = 0;
+
+ if (!(p->context = pa_context_new(pa_mainloop_get_api(p->mainloop), name)))
+ goto fail;
+
+ pa_context_connect(p->context, server, 1, NULL);
+
+ /* Wait until the context is ready */
+ while (pa_context_get_state(p->context) != PA_CONTEXT_READY) {
+ if (iterate(p, 1, &error) < 0)
+ goto fail;
+ }
+
+ if (!(p->stream = pa_stream_new(p->context, stream_name, ss, NULL)))
+ goto fail;
+
+ if (dir == PA_STREAM_PLAYBACK)
+ pa_stream_connect_playback(p->stream, dev, attr, 0, NULL);
+ else
+ pa_stream_connect_record(p->stream, dev, attr, 0);
+
+ /* Wait until the stream is ready */
+ while (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
+ if (iterate(p, 1, &error) < 0)
+ goto fail;
+ }
+
+ pa_stream_set_read_callback(p->stream, read_callback, p);
+
+ return p;
+
+fail:
+ if (rerror)
+ *rerror = error;
+ pa_simple_free(p);
+ return NULL;
+}
+
+void pa_simple_free(pa_simple *s) {
+ assert(s);
+
+ pa_xfree(s->read_data);
+
+ if (s->stream)
+ pa_stream_unref(s->stream);
+
+ if (s->context)
+ pa_context_unref(s->context);
+
+ if (s->mainloop)
+ pa_mainloop_free(s->mainloop);
+
+ pa_xfree(s);
+}
+
+int pa_simple_write(pa_simple *p, const void*data, size_t length, int *rerror) {
+ assert(p && data && p->direction == PA_STREAM_PLAYBACK);
+
+ if (p->dead) {
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return -1;
+ }
+
+ while (length > 0) {
+ size_t l;
+
+ while (!(l = pa_stream_writable_size(p->stream)))
+ if (iterate(p, 1, rerror) < 0)
+ return -1;
+
+ if (l > length)
+ l = length;
+
+ pa_stream_write(p->stream, data, l, NULL, 0);
+ data = (const uint8_t*) data + l;
+ length -= l;
+ }
+
+ /* Make sure that no data is pending for write */
+ if (iterate(p, 0, rerror) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void read_callback(pa_stream *s, const void*data, size_t length, void *userdata) {
+ pa_simple *p = userdata;
+ assert(s && data && length && p);
+
+ if (p->read_data) {
+ pa_log(__FILE__": Buffer overflow, dropping incoming memory blocks.\n");
+ pa_xfree(p->read_data);
+ }
+
+ p->read_data = pa_xmemdup(data, p->read_length = length);
+ p->read_index = 0;
+}
+
+int pa_simple_read(pa_simple *p, void*data, size_t length, int *rerror) {
+ assert(p && data && p->direction == PA_STREAM_RECORD);
+
+ if (p->dead) {
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return -1;
+ }
+
+ while (length > 0) {
+ if (p->read_data) {
+ size_t l = length;
+
+ if (p->read_length <= l)
+ l = p->read_length;
+
+ memcpy(data, (uint8_t*) p->read_data+p->read_index, l);
+
+ data = (uint8_t*) data + l;
+ length -= l;
+
+ p->read_index += l;
+ p->read_length -= l;
+
+ if (!p->read_length) {
+ pa_xfree(p->read_data);
+ p->read_data = NULL;
+ p->read_index = 0;
+ }
+
+ if (!length)
+ return 0;
+
+ assert(!p->read_data);
+ }
+
+ if (iterate(p, 1, rerror) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void drain_or_flush_complete(pa_stream *s, int success, void *userdata) {
+ pa_simple *p = userdata;
+ assert(s && p);
+ if (!success)
+ p->dead = 1;
+}
+
+int pa_simple_drain(pa_simple *p, int *rerror) {
+ pa_operation *o;
+ assert(p && p->direction == PA_STREAM_PLAYBACK);
+
+ if (p->dead) {
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return -1;
+ }
+
+ o = pa_stream_drain(p->stream, drain_or_flush_complete, p);
+
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+ if (iterate(p, 1, rerror) < 0) {
+ pa_operation_cancel(o);
+ pa_operation_unref(o);
+ return -1;
+ }
+ }
+
+ pa_operation_unref(o);
+
+ if (p->dead && rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return p->dead ? -1 : 0;
+}
+
+static void latency_complete(pa_stream *s, const pa_latency_info *l, void *userdata) {
+ pa_simple *p = userdata;
+ assert(s && p);
+
+ if (!l)
+ p->dead = 1;
+ else {
+ int negative = 0;
+ p->latency = pa_stream_get_latency(s, l, &negative);
+ if (negative)
+ p->latency = 0;
+ }
+}
+
+pa_usec_t pa_simple_get_playback_latency(pa_simple *p, int *rerror) {
+ pa_operation *o;
+ assert(p && p->direction == PA_STREAM_PLAYBACK);
+
+ if (p->dead) {
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return (pa_usec_t) -1;
+ }
+
+ p->latency = 0;
+ o = pa_stream_get_latency_info(p->stream, latency_complete, p);
+
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+
+ if (iterate(p, 1, rerror) < 0) {
+ pa_operation_cancel(o);
+ pa_operation_unref(o);
+ return -1;
+ }
+ }
+
+ pa_operation_unref(o);
+
+ if (p->dead && rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return p->dead ? (pa_usec_t) -1 : p->latency;
+}
+
+int pa_simple_flush(pa_simple *p, int *rerror) {
+ pa_operation *o;
+ assert(p && p->direction == PA_STREAM_PLAYBACK);
+
+ if (p->dead) {
+ if (rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return -1;
+ }
+
+ o = pa_stream_flush(p->stream, drain_or_flush_complete, p);
+
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+ if (iterate(p, 1, rerror) < 0) {
+ pa_operation_cancel(o);
+ pa_operation_unref(o);
+ return -1;
+ }
+ }
+
+ pa_operation_unref(o);
+
+ if (p->dead && rerror)
+ *rerror = pa_context_errno(p->context);
+
+ return p->dead ? -1 : 0;
+}
diff --git a/src/polyp/polyplib-simple.h b/src/polyp/polyplib-simple.h
new file mode 100644
index 00000000..b01f30d5
--- /dev/null
+++ b/src/polyp/polyplib-simple.h
@@ -0,0 +1,80 @@
+#ifndef foopolyplibsimplehfoo
+#define foopolyplibsimplehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include "sample.h"
+#include "polyplib-def.h"
+#include <polyp/cdecl.h>
+
+/** \file
+ * A simple but limited synchronous playback and recording
+ * API. This is synchronouse, simplified wrapper around the standard
+ * asynchronous API. */
+
+/** \example pacat-simple.c
+ * A simple playback tool using the simple API */
+
+/** \example parec-simple.c
+ * A simple recording tool using the simple API */
+
+PA_C_DECL_BEGIN
+
+/** \pa_simple
+ * An opaque simple connection object */
+typedef struct pa_simple pa_simple;
+
+/** Create a new connection to the server */
+pa_simple* pa_simple_new(
+ const char *server, /**< Server name, or NULL for default */
+ const char *name, /**< A descriptive name for this client (application name, ...) */
+ pa_stream_direction_t dir, /**< Open this stream for recording or playback? */
+ const char *dev, /**< Sink (resp. source) name, or NULL for default */
+ const char *stream_name, /**< A descriptive name for this client (application name, song title, ...) */
+ const pa_sample_spec *ss, /**< The sample type to use */
+ const pa_buffer_attr *attr, /**< Buffering attributes, or NULL for default */
+ int *error /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */
+ );
+
+/** Close and free the connection to the server. The connection objects becomes invalid when this is called. */
+void pa_simple_free(pa_simple *s);
+
+/** Write some data to the server */
+int pa_simple_write(pa_simple *s, const void*data, size_t length, int *error);
+
+/** Wait until all data already written is played by the daemon */
+int pa_simple_drain(pa_simple *s, int *error);
+
+/** Read some data from the server */
+int pa_simple_read(pa_simple *s, void*data, size_t length, int *error);
+
+/** Return the playback latency. \since 0.5 */
+pa_usec_t pa_simple_get_playback_latency(pa_simple *s, int *error);
+
+/** Flush the playback buffer. \since 0.5 */
+int pa_simple_flush(pa_simple *s, int *error);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-stream.c b/src/polyp/polyplib-stream.c
new file mode 100644
index 00000000..63c9245b
--- /dev/null
+++ b/src/polyp/polyplib-stream.c
@@ -0,0 +1,807 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "polyplib-internal.h"
+#include <polypcore/xmalloc.h>
+#include <polypcore/pstream-util.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+
+#define LATENCY_IPOL_INTERVAL_USEC (10000L)
+
+pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) {
+ pa_stream *s;
+ assert(c);
+ assert(ss);
+
+ if (!pa_sample_spec_valid(ss))
+ return NULL;
+
+ if (map && !pa_channel_map_valid(map))
+ return NULL;
+
+ s = pa_xnew(pa_stream, 1);
+ s->ref = 1;
+ s->context = c;
+ s->mainloop = c->mainloop;
+
+ s->read_callback = NULL;
+ s->read_userdata = NULL;
+ s->write_callback = NULL;
+ s->write_userdata = NULL;
+ s->state_callback = NULL;
+ s->state_userdata = NULL;
+
+ s->direction = PA_STREAM_NODIRECTION;
+ s->name = pa_xstrdup(name);
+ s->sample_spec = *ss;
+
+ if (map)
+ s->channel_map = *map;
+ else
+ pa_channel_map_init_auto(&s->channel_map, ss->channels);
+
+ s->channel = 0;
+ s->channel_valid = 0;
+ s->device_index = PA_INVALID_INDEX;
+ s->requested_bytes = 0;
+ s->state = PA_STREAM_DISCONNECTED;
+ memset(&s->buffer_attr, 0, sizeof(s->buffer_attr));
+
+ s->mcalign = pa_mcalign_new(pa_frame_size(ss), c->memblock_stat);
+
+ s->counter = 0;
+ s->previous_time = 0;
+ s->previous_ipol_time = 0;
+
+ s->corked = 0;
+ s->interpolate = 0;
+
+ s->ipol_usec = 0;
+ memset(&s->ipol_timestamp, 0, sizeof(s->ipol_timestamp));
+ s->ipol_event = NULL;
+ s->ipol_requested = 0;
+
+ PA_LLIST_PREPEND(pa_stream, c->streams, s);
+
+ return pa_stream_ref(s);
+}
+
+static void stream_free(pa_stream *s) {
+ assert(s);
+
+ if (s->ipol_event) {
+ assert(s->mainloop);
+ s->mainloop->time_free(s->ipol_event);
+ }
+
+ pa_mcalign_free(s->mcalign);
+
+ pa_xfree(s->name);
+ pa_xfree(s);
+}
+
+void pa_stream_unref(pa_stream *s) {
+ assert(s && s->ref >= 1);
+
+ if (--(s->ref) == 0)
+ stream_free(s);
+}
+
+pa_stream* pa_stream_ref(pa_stream *s) {
+ assert(s && s->ref >= 1);
+ s->ref++;
+ return s;
+}
+
+pa_stream_state_t pa_stream_get_state(pa_stream *s) {
+ assert(s && s->ref >= 1);
+ return s->state;
+}
+
+pa_context* pa_stream_get_context(pa_stream *s) {
+ assert(s && s->ref >= 1);
+ return s->context;
+}
+
+uint32_t pa_stream_get_index(pa_stream *s) {
+ assert(s && s->ref >= 1);
+ return s->device_index;
+}
+
+void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) {
+ assert(s && s->ref >= 1);
+
+ if (s->state == st)
+ return;
+
+ pa_stream_ref(s);
+
+ s->state = st;
+
+ if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED) && s->context) {
+ if (s->channel_valid)
+ pa_dynarray_put((s->direction == PA_STREAM_PLAYBACK) ? s->context->playback_streams : s->context->record_streams, s->channel, NULL);
+
+ PA_LLIST_REMOVE(pa_stream, s->context->streams, s);
+ pa_stream_unref(s);
+ }
+
+ if (s->state_callback)
+ s->state_callback(s, s->state_userdata);
+
+ pa_stream_unref(s);
+}
+
+void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_stream *s;
+ uint32_t channel;
+ assert(pd && (command == PA_COMMAND_PLAYBACK_STREAM_KILLED || command == PA_COMMAND_RECORD_STREAM_KILLED) && t && c);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_KILLED ? c->playback_streams : c->record_streams, channel)))
+ goto finish;
+
+ c->error = PA_ERROR_KILLED;
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+
+finish:
+ pa_context_unref(c);
+}
+
+void pa_command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s;
+ pa_context *c = userdata;
+ uint32_t bytes, channel;
+ assert(pd && command == PA_COMMAND_REQUEST && t && c);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &bytes) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(c->playback_streams, channel)))
+ goto finish;
+
+ if (s->state != PA_STREAM_READY)
+ goto finish;
+
+ pa_stream_ref(s);
+
+ s->requested_bytes += bytes;
+
+ if (s->requested_bytes && s->write_callback)
+ s->write_callback(s, s->requested_bytes, s->write_userdata);
+
+ pa_stream_unref(s);
+
+finish:
+ pa_context_unref(c);
+}
+
+static void ipol_callback(pa_mainloop_api *m, pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct timeval tv2;
+ pa_stream *s = userdata;
+
+ pa_stream_ref(s);
+
+/* pa_log("requesting new ipol data\n"); */
+
+ if (s->state == PA_STREAM_READY && !s->ipol_requested) {
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+ s->ipol_requested = 1;
+ }
+
+ pa_gettimeofday(&tv2);
+ pa_timeval_add(&tv2, LATENCY_IPOL_INTERVAL_USEC);
+
+ m->time_restart(e, &tv2);
+
+ pa_stream_unref(s);
+}
+
+
+void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s = userdata;
+ assert(pd && s && s->state == PA_STREAM_CREATING);
+
+ pa_stream_ref(s);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(s->context, command, t) < 0)
+ goto finish;
+
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+ goto finish;
+ }
+
+ if (pa_tagstruct_getu32(t, &s->channel) < 0 ||
+ ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->device_index) < 0) ||
+ ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0) ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(s->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ s->channel_valid = 1;
+ pa_dynarray_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, s->channel, s);
+ pa_stream_set_state(s, PA_STREAM_READY);
+
+ if (s->interpolate) {
+ struct timeval tv;
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+
+ pa_gettimeofday(&tv);
+ tv.tv_usec += LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */
+
+ assert(!s->ipol_event);
+ s->ipol_event = s->mainloop->time_new(s->mainloop, &tv, &ipol_callback, s);
+ }
+
+ if (s->requested_bytes && s->ref > 1 && s->write_callback)
+ s->write_callback(s, s->requested_bytes, s->write_userdata);
+
+finish:
+ pa_stream_unref(s);
+}
+
+static void create_stream(pa_stream *s, const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags, const pa_cvolume *volume) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s && s->ref >= 1 && s->state == PA_STREAM_DISCONNECTED);
+
+ pa_stream_ref(s);
+
+ s->interpolate = !!(flags & PA_STREAM_INTERPOLATE_LATENCY);
+ pa_stream_trash_ipol(s);
+
+ if (attr)
+ s->buffer_attr = *attr;
+ else {
+ /* half a second */
+ s->buffer_attr.tlength = pa_bytes_per_second(&s->sample_spec)/2;
+ s->buffer_attr.maxlength = (s->buffer_attr.tlength*3)/2;
+ s->buffer_attr.minreq = s->buffer_attr.tlength/100;
+ s->buffer_attr.prebuf = s->buffer_attr.tlength - s->buffer_attr.minreq;
+ s->buffer_attr.fragsize = s->buffer_attr.tlength/100;
+ }
+
+ pa_stream_set_state(s, PA_STREAM_CREATING);
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+
+ if (!dev) {
+ if (s->direction == PA_STREAM_PLAYBACK)
+ dev = s->context->conf->default_sink;
+ else
+ dev = s->context->conf->default_source;
+ }
+
+ pa_tagstruct_put(t,
+ PA_TAG_U32, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM,
+ PA_TAG_U32, tag = s->context->ctag++,
+ PA_TAG_STRING, s->name,
+ PA_TAG_SAMPLE_SPEC, &s->sample_spec,
+ PA_TAG_CHANNEL_MAP, &s->channel_map,
+ PA_TAG_U32, PA_INVALID_INDEX,
+ PA_TAG_STRING, dev,
+ PA_TAG_U32, s->buffer_attr.maxlength,
+ PA_TAG_BOOLEAN, !!(flags & PA_STREAM_START_CORKED),
+ PA_TAG_INVALID);
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ pa_cvolume cv;
+ pa_tagstruct_put(t,
+ PA_TAG_U32, s->buffer_attr.tlength,
+ PA_TAG_U32, s->buffer_attr.prebuf,
+ PA_TAG_U32, s->buffer_attr.minreq,
+ PA_TAG_INVALID);
+
+ if (!volume) {
+ pa_cvolume_reset(&cv, s->sample_spec.channels);
+ volume = &cv;
+ }
+
+ pa_tagstruct_put_cvolume(t, volume);
+ } else
+ pa_tagstruct_putu32(t, s->buffer_attr.fragsize);
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s);
+
+ pa_stream_unref(s);
+}
+
+void pa_stream_connect_playback(pa_stream *s, const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags, pa_cvolume *volume) {
+ assert(s && s->context->state == PA_CONTEXT_READY && s->ref >= 1);
+ s->direction = PA_STREAM_PLAYBACK;
+ create_stream(s, dev, attr, flags, volume);
+}
+
+void pa_stream_connect_record(pa_stream *s, const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags) {
+ assert(s && s->context->state == PA_CONTEXT_READY && s->ref >= 1);
+ s->direction = PA_STREAM_RECORD;
+ create_stream(s, dev, attr, flags, 0);
+}
+
+void pa_stream_write(pa_stream *s, const void *data, size_t length, void (*free_cb)(void *p), size_t delta) {
+ pa_memchunk chunk;
+ assert(s && s->context && data && length && s->state == PA_STREAM_READY && s->ref >= 1);
+
+ if (free_cb) {
+ chunk.memblock = pa_memblock_new_user((void*) data, length, free_cb, 1, s->context->memblock_stat);
+ assert(chunk.memblock && chunk.memblock->data);
+ } else {
+ chunk.memblock = pa_memblock_new(length, s->context->memblock_stat);
+ assert(chunk.memblock && chunk.memblock->data);
+ memcpy(chunk.memblock->data, data, length);
+ }
+ chunk.index = 0;
+ chunk.length = length;
+
+ pa_pstream_send_memblock(s->context->pstream, s->channel, delta, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ if (length < s->requested_bytes)
+ s->requested_bytes -= length;
+ else
+ s->requested_bytes = 0;
+
+ s->counter += length;
+}
+
+size_t pa_stream_writable_size(pa_stream *s) {
+ assert(s && s->ref >= 1);
+ return s->state == PA_STREAM_READY ? s->requested_bytes : 0;
+}
+
+pa_operation * pa_stream_drain(pa_stream *s, void (*cb) (pa_stream*s, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s && s->ref >= 1 && s->state == PA_STREAM_READY);
+
+ o = pa_operation_new(s->context, s);
+ assert(o);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, PA_COMMAND_DRAIN_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+static void stream_get_latency_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ pa_latency_info i, *p = NULL;
+ struct timeval local, remote, now;
+ assert(pd && o && o->stream && o->context);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ } else if (pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.sink_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.source_usec) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.playing) < 0 ||
+ pa_tagstruct_getu32(t, &i.queue_length) < 0 ||
+ pa_tagstruct_get_timeval(t, &local) < 0 ||
+ pa_tagstruct_get_timeval(t, &remote) < 0 ||
+ pa_tagstruct_getu64(t, &i.counter) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ } else {
+ pa_gettimeofday(&now);
+
+ if (pa_timeval_cmp(&local, &remote) <= 0 && pa_timeval_cmp(&remote, &now) <= 0) {
+ /* local and remote seem to have synchronized clocks */
+
+ if (o->stream->direction == PA_STREAM_PLAYBACK)
+ i.transport_usec = pa_timeval_diff(&remote, &local);
+ else
+ i.transport_usec = pa_timeval_diff(&now, &remote);
+
+ i.synchronized_clocks = 1;
+ i.timestamp = remote;
+ } else {
+ /* clocks are not synchronized, let's estimate latency then */
+ i.transport_usec = pa_timeval_diff(&now, &local)/2;
+ i.synchronized_clocks = 0;
+ i.timestamp = local;
+ pa_timeval_add(&i.timestamp, i.transport_usec);
+ }
+
+ if (o->stream->interpolate) {
+/* pa_log("new interpol data\n"); */
+ o->stream->ipol_timestamp = i.timestamp;
+ o->stream->ipol_usec = pa_stream_get_time(o->stream, &i);
+ o->stream->ipol_requested = 0;
+ }
+
+ p = &i;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_stream *s, const pa_latency_info *_i, void *_userdata) = (void (*)(pa_stream *s, const pa_latency_info *_i, void *_userdata)) o->callback;
+ cb(o->stream, p, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_stream_get_latency_info(pa_stream *s, void (*cb)(pa_stream *p, const pa_latency_info*i, void *userdata), void *userdata) {
+ uint32_t tag;
+ pa_operation *o;
+ pa_tagstruct *t;
+ struct timeval now;
+ assert(s && s->direction != PA_STREAM_UPLOAD);
+
+ o = pa_operation_new(s->context, s);
+ assert(o);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_GET_PLAYBACK_LATENCY : PA_COMMAND_GET_RECORD_LATENCY);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+
+ pa_gettimeofday(&now);
+ pa_tagstruct_put_timeval(t, &now);
+ pa_tagstruct_putu64(t, s->counter);
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_info_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s = userdata;
+ assert(pd && s && s->ref >= 1);
+
+ pa_stream_ref(s);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(s->context, command, t) < 0)
+ goto finish;
+
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+ goto finish;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(s->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ pa_stream_set_state(s, PA_STREAM_TERMINATED);
+
+finish:
+ pa_stream_unref(s);
+}
+
+void pa_stream_disconnect(pa_stream *s) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s && s->ref >= 1);
+
+ if (!s->channel_valid || !s->context->state == PA_CONTEXT_READY)
+ return;
+
+ pa_stream_ref(s);
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+
+ pa_tagstruct_putu32(t, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_DELETE_PLAYBACK_STREAM :
+ (s->direction == PA_STREAM_RECORD ? PA_COMMAND_DELETE_RECORD_STREAM : PA_COMMAND_DELETE_UPLOAD_STREAM));
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s);
+
+ pa_stream_unref(s);
+}
+
+void pa_stream_set_read_callback(pa_stream *s, void (*cb)(pa_stream *p, const void*data, size_t length, void *userdata), void *userdata) {
+ assert(s && s->ref >= 1);
+ s->read_callback = cb;
+ s->read_userdata = userdata;
+}
+
+void pa_stream_set_write_callback(pa_stream *s, void (*cb)(pa_stream *p, size_t length, void *userdata), void *userdata) {
+ assert(s && s->ref >= 1);
+ s->write_callback = cb;
+ s->write_userdata = userdata;
+}
+
+void pa_stream_set_state_callback(pa_stream *s, void (*cb)(pa_stream *s, void *userdata), void *userdata) {
+ assert(s && s->ref >= 1);
+ s->state_callback = cb;
+ s->state_userdata = userdata;
+}
+
+void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+ assert(pd && o && o->context && o->ref >= 1);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ void (*cb)(pa_stream *s, int _success, void *_userdata) = (void (*)(pa_stream *s, int _success, void *_userdata)) o->callback;
+ cb(o->stream, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_stream_cork(pa_stream *s, int b, void (*cb) (pa_stream*s, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s && s->ref >= 1 && s->state == PA_STREAM_READY);
+
+ if (s->interpolate) {
+ if (!s->corked && b)
+ /* Pausing */
+ s->ipol_usec = pa_stream_get_interpolated_time(s);
+ else if (s->corked && !b)
+ /* Unpausing */
+ pa_gettimeofday(&s->ipol_timestamp);
+ }
+
+ s->corked = b;
+
+ o = pa_operation_new(s->context, s);
+ assert(o);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CORK_PLAYBACK_STREAM : PA_COMMAND_CORK_RECORD_STREAM);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_put_boolean(t, !!b);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, o);
+
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+
+ return pa_operation_ref(o);
+}
+
+static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+ assert(s && s->ref >= 1 && s->state == PA_STREAM_READY);
+
+ o = pa_operation_new(s->context, s);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, command);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+pa_operation* pa_stream_flush(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ o = stream_send_simple_command(s, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM, cb, userdata);
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+ return o;
+}
+
+pa_operation* pa_stream_prebuf(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata);
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+ return o;
+}
+
+pa_operation* pa_stream_trigger(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata);
+ pa_operation_unref(pa_stream_get_latency_info(s, NULL, NULL));
+ return o;
+}
+
+pa_operation* pa_stream_set_name(pa_stream *s, const char *name, void(*cb)(pa_stream*c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(s && s->ref >= 1 && s->state == PA_STREAM_READY && name && s->direction != PA_STREAM_UPLOAD);
+
+ o = pa_operation_new(s->context, s);
+ assert(o);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ assert(t);
+ pa_tagstruct_putu32(t, s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME);
+ pa_tagstruct_putu32(t, tag = s->context->ctag++);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+uint64_t pa_stream_get_counter(pa_stream *s) {
+ assert(s);
+ return s->counter;
+}
+
+pa_usec_t pa_stream_get_time(pa_stream *s, const pa_latency_info *i) {
+ pa_usec_t usec;
+ assert(s);
+
+ usec = pa_bytes_to_usec(i->counter, &s->sample_spec);
+
+ if (i) {
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ pa_usec_t latency = i->transport_usec + i->buffer_usec + i->sink_usec;
+ if (usec < latency)
+ usec = 0;
+ else
+ usec -= latency;
+
+ } else if (s->direction == PA_STREAM_RECORD) {
+ usec += i->source_usec + i->buffer_usec + i->transport_usec;
+
+ if (usec > i->sink_usec)
+ usec -= i->sink_usec;
+ else
+ usec = 0;
+ }
+ }
+
+ if (usec < s->previous_time)
+ usec = s->previous_time;
+
+ s->previous_time = usec;
+
+ return usec;
+}
+
+static pa_usec_t time_counter_diff(pa_stream *s, pa_usec_t t, pa_usec_t c, int *negative) {
+ assert(s);
+
+ if (negative)
+ *negative = 0;
+
+ if (c < t) {
+ if (s->direction == PA_STREAM_RECORD) {
+ if (negative)
+ *negative = 1;
+
+ return t-c;
+ } else
+ return 0;
+ } else
+ return c-t;
+}
+
+pa_usec_t pa_stream_get_latency(pa_stream *s, const pa_latency_info *i, int *negative) {
+ pa_usec_t t, c;
+ assert(s && i);
+
+ t = pa_stream_get_time(s, i);
+ c = pa_bytes_to_usec(s->counter, &s->sample_spec);
+
+ return time_counter_diff(s, t, c, negative);
+}
+
+const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) {
+ assert(s);
+ return &s->sample_spec;
+}
+
+void pa_stream_trash_ipol(pa_stream *s) {
+ assert(s);
+
+ if (!s->interpolate)
+ return;
+
+ memset(&s->ipol_timestamp, 0, sizeof(s->ipol_timestamp));
+ s->ipol_usec = 0;
+}
+
+pa_usec_t pa_stream_get_interpolated_time(pa_stream *s) {
+ pa_usec_t usec;
+ assert(s && s->interpolate);
+
+ if (s->corked)
+ usec = s->ipol_usec;
+ else {
+ if (s->ipol_timestamp.tv_sec == 0)
+ usec = 0;
+ else
+ usec = s->ipol_usec + pa_timeval_age(&s->ipol_timestamp);
+ }
+
+ if (usec < s->previous_ipol_time)
+ usec = s->previous_ipol_time;
+
+ s->previous_ipol_time = usec;
+
+ return usec;
+}
+
+pa_usec_t pa_stream_get_interpolated_latency(pa_stream *s, int *negative) {
+ pa_usec_t t, c;
+ assert(s && s->interpolate);
+
+ t = pa_stream_get_interpolated_time(s);
+ c = pa_bytes_to_usec(s->counter, &s->sample_spec);
+ return time_counter_diff(s, t, c, negative);
+}
diff --git a/src/polyp/polyplib-stream.h b/src/polyp/polyplib-stream.h
new file mode 100644
index 00000000..bc828b71
--- /dev/null
+++ b/src/polyp/polyplib-stream.h
@@ -0,0 +1,181 @@
+#ifndef foopolyplibstreamhfoo
+#define foopolyplibstreamhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <polyp/sample.h>
+#include <polyp/channelmap.h>
+#include <polyp/volume.h>
+#include <polyp/polyplib-def.h>
+#include <polyp/cdecl.h>
+#include <polyp/polyplib-operation.h>
+
+/** \file
+ * Audio streams for input, output and sample upload */
+
+PA_C_DECL_BEGIN
+
+/** \pa_stream
+ * An opaque stream for playback or recording */
+typedef struct pa_stream pa_stream;
+
+/** Create a new, unconnected stream with the specified name and sample type */
+pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map);
+
+/** Decrease the reference counter by one */
+void pa_stream_unref(pa_stream *s);
+
+/** Increase the reference counter by one */
+pa_stream *pa_stream_ref(pa_stream *s);
+
+/** Return the current state of the stream */
+pa_stream_state_t pa_stream_get_state(pa_stream *p);
+
+/** Return the context this stream is attached to */
+pa_context* pa_stream_get_context(pa_stream *p);
+
+/** Return the device (sink input or source output) index this stream is connected to */
+uint32_t pa_stream_get_index(pa_stream *s);
+
+/** Connect the stream to a sink */
+void pa_stream_connect_playback(
+ pa_stream *s,
+ const char *dev,
+ const pa_buffer_attr *attr,
+ pa_stream_flags_t flags,
+ pa_cvolume *volume);
+
+/** Connect the stream to a source */
+void pa_stream_connect_record(
+ pa_stream *s,
+ const char *dev,
+ const pa_buffer_attr *attr,
+ pa_stream_flags_t flags);
+
+/** Disconnect a stream from a source/sink */
+void pa_stream_disconnect(pa_stream *s);
+
+/** Write some data to the server (for playback sinks), if free_cb is
+ * non-NULL this routine is called when all data has been written out
+ * and an internal reference to the specified data is kept, the data
+ * is not copied. If NULL, the data is copied into an internal
+ * buffer. */
+void pa_stream_write(pa_stream *p /**< The stream to use */,
+ const void *data /**< The data to write */,
+ size_t length /**< The length of the data to write */,
+ void (*free_cb)(void *p) /**< A cleanup routine for the data or NULL to request an internal copy */,
+ size_t delta /**< Drop this many
+ bytes in the playback
+ buffer before writing
+ this data. Use
+ (size_t) -1 for
+ clearing the whole
+ playback
+ buffer. Normally you
+ will specify 0 here,
+ i.e. append to the
+ playback buffer. If
+ the value given here
+ is greater than the
+ buffered data length
+ the buffer is cleared
+ and the data is
+ written to the
+ buffer's start. This
+ value is ignored on
+ upload streams. */);
+
+/** Return the amount of bytes that may be written using pa_stream_write() */
+size_t pa_stream_writable_size(pa_stream *p);
+
+/** Drain a playback stream */
+pa_operation* pa_stream_drain(pa_stream *s, void (*cb) (pa_stream*s, int success, void *userdata), void *userdata);
+
+/** Get the playback latency of a stream */
+pa_operation* pa_stream_get_latency_info(pa_stream *p, void (*cb)(pa_stream *p, const pa_latency_info *i, void *userdata), void *userdata);
+
+/** Set the callback function that is called whenever the state of the stream changes */
+void pa_stream_set_state_callback(pa_stream *s, void (*cb)(pa_stream *s, void *userdata), void *userdata);
+
+/** Set the callback function that is called when new data may be
+ * written to the stream. */
+void pa_stream_set_write_callback(pa_stream *p, void (*cb)(pa_stream *p, size_t length, void *userdata), void *userdata);
+
+/** Set the callback function that is called when new data is available from the stream */
+void pa_stream_set_read_callback(pa_stream *p, void (*cb)(pa_stream *p, const void*data, size_t length, void *userdata), void *userdata);
+
+/** Pause (or resume) playback of this stream temporarily. Available on both playback and recording streams. \since 0.3 */
+pa_operation* pa_stream_cork(pa_stream *s, int b, void (*cb) (pa_stream*s, int success, void *userdata), void *userdata);
+
+/** Flush the playback buffer of this stream. Most of the time you're
+ * better off using the parameter delta of pa_stream_write() instead of this
+ * function. Available on both playback and recording streams. \since 0.3 */
+pa_operation* pa_stream_flush(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata);
+
+/** Reenable prebuffering. Available for playback streams only. \since 0.6 */
+pa_operation* pa_stream_prebuf(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata);
+
+/** Request immediate start of playback on this stream. This disables
+ * prebuffering as specified in the pa_buffer_attr structure. Available for playback streams only. \since
+ * 0.3 */
+pa_operation* pa_stream_trigger(pa_stream *s, void (*cb)(pa_stream *s, int success, void *userdata), void *userdata);
+
+/** Rename the stream. \since 0.5 */
+pa_operation* pa_stream_set_name(pa_stream *s, const char *name, void(*cb)(pa_stream*c, int success, void *userdata), void *userdata);
+
+/** Return the total number of bytes written to/read from the
+ * stream. This counter is not reset on pa_stream_flush(), you may do
+ * this yourself using pa_stream_reset_counter(). \since 0.6 */
+uint64_t pa_stream_get_counter(pa_stream *s);
+
+/** Return the current playback/recording time. This is based on the
+ * counter accessible with pa_stream_get_counter(). This function
+ * requires a pa_latency_info structure as argument, which should be
+ * acquired using pa_stream_get_latency(). \since 0.6 */
+pa_usec_t pa_stream_get_time(pa_stream *s, const pa_latency_info *i);
+
+/** Return the total stream latency. Thus function requires a
+ * pa_latency_info structure as argument, which should be aquired
+ * using pa_stream_get_latency(). In case the stream is a monitoring
+ * stream the result can be negative, i.e. the captured samples are
+ * not yet played. In this case *negative is set to 1. \since 0.6 */
+pa_usec_t pa_stream_get_latency(pa_stream *s, const pa_latency_info *i, int *negative);
+
+/** Return the interpolated playback/recording time. Requires the
+ * PA_STREAM_INTERPOLATE_LATENCY bit set when creating the stream. In
+ * contrast to pa_stream_get_latency() this function doesn't require
+ * a whole roundtrip for response. \since 0.6 */
+pa_usec_t pa_stream_get_interpolated_time(pa_stream *s);
+
+/** Return the interpolated playback/recording latency. Requires the
+ * PA_STREAM_INTERPOLATE_LATENCY bit set when creating the
+ * stream. \since 0.6 */
+pa_usec_t pa_stream_get_interpolated_latency(pa_stream *s, int *negative);
+
+/** Return a pointer to the streams sample specification. \since 0.6 */
+const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-subscribe.c b/src/polyp/polyplib-subscribe.c
new file mode 100644
index 00000000..13fcfb42
--- /dev/null
+++ b/src/polyp/polyplib-subscribe.c
@@ -0,0 +1,81 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "polyplib-subscribe.h"
+#include "polyplib-internal.h"
+#include <polypcore/pstream-util.h>
+#include <polypcore/gccmacro.h>
+
+void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_subscription_event_type_t e;
+ uint32_t index;
+ assert(pd && command == PA_COMMAND_SUBSCRIBE_EVENT && t && c);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &e) < 0 ||
+ pa_tagstruct_getu32(t, &index) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERROR_PROTOCOL);
+ goto finish;
+ }
+
+ if (c->subscribe_callback)
+ c->subscribe_callback(c, e, index, c->subscribe_userdata);
+
+finish:
+ pa_context_unref(c);
+}
+
+
+pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, void (*cb)(pa_context *c, int success, void *userdata), void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(c);
+
+ o = pa_operation_new(c, NULL);
+ o->callback = (pa_operation_callback) cb;
+ o->userdata = userdata;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE);
+ pa_tagstruct_putu32(t, tag = c->ctag++);
+ pa_tagstruct_putu32(t, m);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, o);
+
+ return pa_operation_ref(o);
+}
+
+void pa_context_set_subscribe_callback(pa_context *c, void (*cb)(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata), void *userdata) {
+ assert(c);
+ c->subscribe_callback = cb;
+ c->subscribe_userdata = userdata;
+}
diff --git a/src/polyp/polyplib-subscribe.h b/src/polyp/polyplib-subscribe.h
new file mode 100644
index 00000000..920c9853
--- /dev/null
+++ b/src/polyp/polyplib-subscribe.h
@@ -0,0 +1,47 @@
+#ifndef foopolyplibsubscribehfoo
+#define foopolyplibsubscribehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+#include <polyp/polyplib-def.h>
+#include <polyp/polyplib-context.h>
+#include <polyp/cdecl.h>
+
+/** \file
+ * Daemon introspection event subscription subsystem. Use this
+ * to be notified whenever the internal layout of daemon changes:
+ * i.e. entities such as sinks or sources are create, removed or
+ * modified. */
+
+PA_C_DECL_BEGIN
+
+/** Enable event notification */
+pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, void (*cb)(pa_context *c, int success, void *userdata), void *userdata);
+
+/** Set the context specific call back function that is called whenever the state of the daemon changes */
+void pa_context_set_subscribe_callback(pa_context *c, void (*cb)(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata), void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib-version.h.in b/src/polyp/polyplib-version.h.in
new file mode 100644
index 00000000..89e0a0e5
--- /dev/null
+++ b/src/polyp/polyplib-version.h.in
@@ -0,0 +1,47 @@
+#ifndef foopolyplibversionhfoo /*-*-C-*-*/
+#define foopolyplibversionhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* WARNING: Make sure to edit the real source file polyplib-version.h.in! */
+
+/** \file
+ * Define header version */
+
+PA_C_DECL_BEGIN
+
+/** Return the version of the header files. Keep in mind that this is
+a macro and not a function, so it is impossible to get the pointer of
+it. */
+#define pa_get_headers_version() ("@PACKAGE_VERSION@")
+
+/** Return the version of the library the current application is linked to. */
+const char* pa_get_library_version(void);
+
+/** The current API version. Version 6 relates to polypaudio
+ * 0.6. Prior versions (i.e. Polypaudio 0.5.1 and older) have
+ * PA_API_VERSION undefined. */
+#define PA_API_VERSION @PA_API_VERSION@
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/polyplib.h b/src/polyp/polyplib.h
new file mode 100644
index 00000000..b9b9b447
--- /dev/null
+++ b/src/polyp/polyplib.h
@@ -0,0 +1,86 @@
+#ifndef foopolyplibhfoo
+#define foopolyplibhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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.
+
+ polypaudio 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 polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <polyp/cdecl.h>
+#include <polyp/mainloop-api.h>
+#include <polyp/sample.h>
+#include <polyp/polyplib-def.h>
+#include <polyp/polyplib-context.h>
+#include <polyp/polyplib-stream.h>
+#include <polyp/polyplib-introspect.h>
+#include <polyp/polyplib-subscribe.h>
+#include <polyp/polyplib-scache.h>
+#include <polyp/polyplib-version.h>
+
+/** \file
+ * Include all polyplib header file at once. The following files are included: \ref mainloop-api.h, \ref sample.h,
+ * \ref polyplib-def.h, \ref polyplib-context.h, \ref polyplib-stream.h,
+ * \ref polyplib-introspect.h, \ref polyplib-subscribe.h and \ref polyplib-scache.h \ref polyplib-version.h
+ * at once */
+
+/** \mainpage
+ *
+ * \section intro_sec Introduction
+ *
+ * This document describes the client API for the polypaudio sound
+ * server. The API comes in two flavours:
+ *
+ * \li The complete but somewhat complicated to use asynchronous API
+ * \li And the simplified, easy to use, but limited synchronous API
+ *
+ * The polypaudio client libraries are thread safe as long as all
+ * objects created by any library function are accessed from the thread
+ * that created them only.
+ *
+ * \section simple_sec Simple API
+ *
+ * Use this if you develop your program in synchronous style and just
+ * need a way to play or record data on the sound server. See
+ * \ref polyplib-simple.h for more details.
+ *
+ * \section async_api Asynchronous API
+ *
+ * Use this if you develop your programs in asynchronous, main loop
+ * based style or want to use advanced features of the polypaudio
+ * API. A good starting point is \ref polyplib-context.h
+ *
+ * The asynchronous API relies on an abstract main loop API that is
+ * described in \ref mainloop-api.h. Two distinct implementations are
+ * available:
+ *
+ * \li \ref mainloop.h: a minimal but fast implementation based on poll()
+ * \li \ref glib-mainloop.h: a wrapper around GLIB's main loop
+ *
+ * UNIX signals may be hooked to a main loop using the functions from
+ * \ref mainloop-signal.h
+ *
+ * \section pkgconfig pkg-config
+ *
+ * The polypaudio libraries provide pkg-config snippets for the different modules. To use the
+ * asynchronous API use "polyplib" as pkg-config file. GLIB main loop
+ * support is available as "polyplib-glib-mainloop". The simple
+ * synchronous API is available as "polyplib-simple".
+ */
+
+#endif
diff --git a/src/polyp/sample.c b/src/polyp/sample.c
new file mode 100644
index 00000000..d587170c
--- /dev/null
+++ b/src/polyp/sample.c
@@ -0,0 +1,149 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+
+#include "sample.h"
+
+size_t pa_sample_size(const pa_sample_spec *spec) {
+ assert(spec);
+
+ switch (spec->format) {
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW:
+ return 1;
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ return 2;
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ return 4;
+ default:
+ assert(0);
+ }
+}
+
+size_t pa_frame_size(const pa_sample_spec *spec) {
+ assert(spec);
+
+ return pa_sample_size(spec) * spec->channels;
+}
+
+size_t pa_bytes_per_second(const pa_sample_spec *spec) {
+ assert(spec);
+ return spec->rate*pa_frame_size(spec);
+}
+
+pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) {
+ assert(spec);
+
+ return (pa_usec_t) (((double) length/pa_frame_size(spec)*1000000)/spec->rate);
+}
+
+int pa_sample_spec_valid(const pa_sample_spec *spec) {
+ assert(spec);
+
+ if (spec->rate <= 0 ||
+ spec->channels <= 0 ||
+ spec->channels > PA_CHANNELS_MAX ||
+ spec->format >= PA_SAMPLE_MAX ||
+ spec->format < 0)
+ return 0;
+
+ return 1;
+}
+
+int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) {
+ assert(a && b);
+
+ return (a->format == b->format) && (a->rate == b->rate) && (a->channels == b->channels);
+}
+
+const char *pa_sample_format_to_string(pa_sample_format_t f) {
+ static const char* const table[]= {
+ [PA_SAMPLE_U8] = "u8",
+ [PA_SAMPLE_ALAW] = "aLaw",
+ [PA_SAMPLE_ULAW] = "uLaw",
+ [PA_SAMPLE_S16LE] = "s16le",
+ [PA_SAMPLE_S16BE] = "s16be",
+ [PA_SAMPLE_FLOAT32LE] = "float32le",
+ [PA_SAMPLE_FLOAT32BE] = "float32be",
+ };
+
+ if (f >= PA_SAMPLE_MAX)
+ return NULL;
+
+ return table[f];
+}
+
+char *pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) {
+ assert(s && l && spec);
+
+ if (!pa_sample_spec_valid(spec))
+ snprintf(s, l, "Invalid");
+ else
+ snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate);
+
+ return s;
+}
+
+void pa_bytes_snprint(char *s, size_t l, unsigned v) {
+ if (v >= ((unsigned) 1024)*1024*1024)
+ snprintf(s, l, "%0.1f GB", ((double) v)/1024/1024/1024);
+ else if (v >= ((unsigned) 1024)*1024)
+ snprintf(s, l, "%0.1f MB", ((double) v)/1024/1024);
+ else if (v >= (unsigned) 1024)
+ snprintf(s, l, "%0.1f KB", ((double) v)/1024);
+ else
+ snprintf(s, l, "%u B", (unsigned) v);
+}
+
+pa_sample_format_t pa_parse_sample_format(const char *format) {
+
+ if (strcasecmp(format, "s16le") == 0)
+ return PA_SAMPLE_S16LE;
+ else if (strcasecmp(format, "s16be") == 0)
+ return PA_SAMPLE_S16BE;
+ else if (strcasecmp(format, "s16ne") == 0 || strcasecmp(format, "s16") == 0 || strcasecmp(format, "16") == 0)
+ return PA_SAMPLE_S16NE;
+ else if (strcasecmp(format, "u8") == 0 || strcasecmp(format, "8") == 0)
+ return PA_SAMPLE_U8;
+ else if (strcasecmp(format, "float32") == 0 || strcasecmp(format, "float32ne") == 0)
+ return PA_SAMPLE_FLOAT32;
+ else if (strcasecmp(format, "float32le") == 0)
+ return PA_SAMPLE_FLOAT32LE;
+ else if (strcasecmp(format, "float32be") == 0)
+ return PA_SAMPLE_FLOAT32BE;
+ else if (strcasecmp(format, "ulaw") == 0)
+ return PA_SAMPLE_ULAW;
+ else if (strcasecmp(format, "alaw") == 0)
+ return PA_SAMPLE_ALAW;
+
+ return -1;
+}
diff --git a/src/polyp/sample.h b/src/polyp/sample.h
new file mode 100644
index 00000000..c1b98f1c
--- /dev/null
+++ b/src/polyp/sample.h
@@ -0,0 +1,120 @@
+#ifndef foosamplehfoo
+#define foosamplehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <math.h>
+
+#include <polyp/cdecl.h>
+
+/** \file
+ * Constants and routines for sample type handling */
+
+PA_C_DECL_BEGIN
+
+/* Maximum allowed channels */
+#define PA_CHANNELS_MAX 16
+
+/** Sample format */
+typedef enum pa_sample_format {
+ PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */
+ PA_SAMPLE_ALAW, /**< 8 Bit a-Law */
+ PA_SAMPLE_ULAW, /**< 8 Bit mu-Law */
+ PA_SAMPLE_S16LE, /**< Signed 16 Bit PCM, little endian (PC) */
+ PA_SAMPLE_S16BE, /**< Signed 16 Bit PCM, big endian */
+ PA_SAMPLE_FLOAT32LE, /**< 32 Bit IEEE floating point, little endian, range -1..1 */
+ PA_SAMPLE_FLOAT32BE, /**< 32 Bit IEEE floating point, big endian, range -1..1 */
+ PA_SAMPLE_MAX, /**< Upper limit of valid sample types */
+ PA_SAMPLE_INVALID = -1 /**< An invalid value */
+} pa_sample_format_t;
+
+#ifdef WORDS_BIGENDIAN
+/** Signed 16 Bit PCM, native endian */
+#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE
+/** 32 Bit IEEE floating point, native endian */
+#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE
+/** Signed 16 Bit PCM reverse endian */
+#define PA_SAMPLE_S16RE PA_SAMPLE_S16LE
+/** 32 Bit IEEE floating point, reverse endian */
+#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE
+#else
+/** Signed 16 Bit PCM, native endian */
+#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE
+/** 32 Bit IEEE floating point, native endian */
+#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE
+/** Signed 16 Bit PCM reverse endian */
+#define PA_SAMPLE_S16RE PA_SAMPLE_S16BE
+/** 32 Bit IEEE floating point, reverse endian */
+#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE
+#endif
+
+/** A Shortcut for PA_SAMPLE_FLOAT32NE */
+#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32NE
+
+/** A sample format and attribute specification */
+typedef struct pa_sample_spec {
+ pa_sample_format_t format; /**< The sample format */
+ uint32_t rate; /**< The sample rate. (e.g. 44100) */
+ uint8_t channels; /**< Audio channels. (1 for mono, 2 for stereo, ...) */
+} pa_sample_spec;
+
+/** Type for usec specifications (unsigned). May be either 32 or 64 bit, depending on the architecture */
+typedef uint64_t pa_usec_t;
+
+/** Return the amount of bytes playback of a second of audio with the specified sample type takes */
+size_t pa_bytes_per_second(const pa_sample_spec *spec);
+
+/** Return the size of a frame with the specific sample type */
+size_t pa_frame_size(const pa_sample_spec *spec);
+
+/** Return the size of a sample with the specific sample type */
+size_t pa_sample_size(const pa_sample_spec *spec);
+
+/** Calculate the time the specified bytes take to play with the specified sample type */
+pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec);
+
+/** Return non-zero when the sample type specification is valid */
+int pa_sample_spec_valid(const pa_sample_spec *spec);
+
+/** Return non-zero when the two sample type specifications match */
+int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b);
+
+/* Return a descriptive string for the specified sample format. \since 0.8 */
+const char *pa_sample_format_to_string(pa_sample_format_t f);
+
+/** Parse a sample format text. Inverse of pa_sample_format_to_string() */
+pa_sample_format_t pa_parse_sample_format(const char *format);
+
+/** Maximum required string length for pa_sample_spec_snprint() */
+#define PA_SAMPLE_SPEC_SNPRINT_MAX 32
+
+/** Pretty print a sample type specification to a string */
+char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec);
+
+/** Pretty print a byte size value. (i.e. "2.5 MB") */
+void pa_bytes_snprint(char *s, size_t l, unsigned v);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/polyp/volume.c b/src/polyp/volume.c
new file mode 100644
index 00000000..0f153141
--- /dev/null
+++ b/src/polyp/volume.c
@@ -0,0 +1,176 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "volume.h"
+
+int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) {
+ int i;
+ assert(a);
+ assert(b);
+
+ if (a->channels != b->channels)
+ return 0;
+
+ for (i = 0; i < a->channels; i++)
+ if (a->values[i] != b->values[i])
+ return 0;
+
+ return 1;
+}
+
+pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) {
+ int i;
+
+ assert(a);
+ assert(channels > 0);
+ assert(channels <= PA_CHANNELS_MAX);
+
+ a->channels = channels;
+
+ for (i = 0; i < a->channels; i++)
+ a->values[i] = v;
+
+ return a;
+}
+
+pa_volume_t pa_cvolume_avg(const pa_cvolume *a) {
+ uint64_t sum = 0;
+ int i;
+ assert(a);
+
+ for (i = 0; i < a->channels; i++)
+ sum += a->values[i];
+
+ sum /= a->channels;
+
+ return (pa_volume_t) sum;
+}
+
+pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) {
+ return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a)* pa_sw_volume_to_linear(b));
+}
+
+#define USER_DECIBEL_RANGE 30
+
+pa_volume_t pa_sw_volume_from_dB(double dB) {
+ if (dB <= -USER_DECIBEL_RANGE)
+ return PA_VOLUME_MUTED;
+
+ return (pa_volume_t) ((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM);
+}
+
+double pa_sw_volume_to_dB(pa_volume_t v) {
+ if (v == PA_VOLUME_MUTED)
+ return PA_DECIBEL_MININFTY;
+
+ return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE;
+}
+
+pa_volume_t pa_sw_volume_from_linear(double v) {
+
+ if (v <= 0)
+ return PA_VOLUME_MUTED;
+
+ if (v > .999 && v < 1.001)
+ return PA_VOLUME_NORM;
+
+ return pa_sw_volume_from_dB(20*log10(v));
+}
+
+double pa_sw_volume_to_linear(pa_volume_t v) {
+
+ if (v == PA_VOLUME_MUTED)
+ return 0;
+
+ return pow(10, pa_sw_volume_to_dB(v)/20);
+}
+
+char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) {
+ unsigned channel;
+ int first = 1;
+ char *e;
+
+ assert(s);
+ assert(l > 0);
+ assert(c);
+
+ *(e = s) = 0;
+
+ for (channel = 0; channel < c->channels && l > 1; channel++) {
+ l -= snprintf(e, l, "%s%u: %3u%%",
+ first ? "" : " ",
+ channel,
+ (c->values[channel]*100)/PA_VOLUME_NORM);
+
+ e = strchr(e, 0);
+ first = 0;
+ }
+
+ return s;
+}
+
+/** Return non-zero if the volume of all channels is equal to the specified value */
+int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) {
+ unsigned c;
+ assert(a);
+
+ for (c = 0; c < a->channels; c++)
+ if (a->values[c] != v)
+ return 0;
+
+ return 1;
+}
+
+pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) {
+ unsigned i;
+
+ assert(dest);
+ assert(a);
+ assert(b);
+
+ for (i = 0; i < a->channels || i < b->channels || i < PA_CHANNELS_MAX; i++) {
+
+ dest->values[i] = pa_sw_volume_multiply(
+ i < a->channels ? a->values[i] : PA_VOLUME_NORM,
+ i < b->channels ? b->values[i] : PA_VOLUME_NORM);
+ }
+
+ dest->channels = i;
+
+ return dest;
+}
+
+int pa_cvolume_valid(const pa_cvolume *v) {
+ assert(v);
+
+ if (v->channels <= 0 || v->channels > PA_CHANNELS_MAX)
+ return 0;
+
+ return 1;
+}
diff --git a/src/polyp/volume.h b/src/polyp/volume.h
new file mode 100644
index 00000000..b2a48084
--- /dev/null
+++ b/src/polyp/volume.h
@@ -0,0 +1,107 @@
+#ifndef foovolumehfoo
+#define foovolumehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <polyp/cdecl.h>
+#include <polyp/sample.h>
+
+/** \file
+ * Constants and routines for volume handling */
+
+PA_C_DECL_BEGIN
+
+/** Volume specification:
+ * PA_VOLUME_MUTED: silence;
+ * < PA_VOLUME_NORM: decreased volume;
+ * PA_VOLUME_NORM: normal volume;
+ * > PA_VOLUME_NORM: increased volume */
+typedef uint32_t pa_volume_t;
+
+/** Normal volume (100%) */
+#define PA_VOLUME_NORM (0x10000)
+
+/** Muted volume (0%) */
+#define PA_VOLUME_MUTED (0)
+
+/** A structure encapsulating a per-channel volume */
+typedef struct pa_cvolume {
+ uint8_t channels;
+ pa_volume_t values[PA_CHANNELS_MAX];
+} pa_cvolume;
+
+/** Return non-zero when *a == *b */
+int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b);
+
+/** Set the volume of all channels to PA_VOLUME_NORM */
+#define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM)
+
+/** Set the volume of all channels to PA_VOLUME_MUTED */
+#define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED)
+
+/** Set the volume of all channels to the specified parameter */
+pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v);
+
+/** Pretty print a volume structure */
+#define PA_CVOLUME_SNPRINT_MAX 64
+char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c);
+
+/** Return the average volume of all channels */
+pa_volume_t pa_cvolume_avg(const pa_cvolume *a);
+
+/** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */
+int pa_cvolume_valid(const pa_cvolume *v);
+
+/** Return non-zero if the volume of all channels is equal to the specified value */
+int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v);
+
+#define pa_cvolume_is_muted(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_MUTED)
+#define pa_cvolume_is_norm(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_NORM)
+
+/** Multiply two volumes specifications, return the result. This uses PA_VOLUME_NORM as neutral element of multiplication. */
+pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b);
+
+pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b);
+
+/** Convert a decibel value to a volume. \since 0.4 */
+pa_volume_t pa_sw_volume_from_dB(double f);
+
+/** Convert a volume to a decibel value. \since 0.4 */
+double pa_sw_volume_to_dB(pa_volume_t v);
+
+/** Convert a linear factor to a volume. \since 0.8 */
+pa_volume_t pa_sw_volume_from_linear(double v);
+
+/** Convert a volume to a linear factor. \since 0.8 */
+double pa_sw_volume_to_linear(pa_volume_t v);
+
+#ifdef INFINITY
+#define PA_DECIBEL_MININFTY (-INFINITY)
+#else
+/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). \since 0.4 */
+#define PA_DECIBEL_MININFTY (-200)
+#endif
+
+PA_C_DECL_END
+
+#endif