summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2006-02-16 19:19:58 +0000
committerPierre Ossman <ossman@cendio.se>2006-02-16 19:19:58 +0000
commite205b25d65ccb380fa158711e24d55b6de5d9bc1 (patch)
tree470b4ced153c9ff18b8e35deb05c7a95efc838fc /src
parent5b881e62282f26b353635120935d114e0c7c3f3c (diff)
Reorganised the source tree. We now have src/ with a couple of subdirs:
* daemon/ - Contains the files specific to the polypaudio daemon. * modules/ - All loadable modules. * polyp/ - Files that are part of the public, application interface or are only used in libpolyp. * polypcore/ - All other shared files. * tests/ - Test programs. * utils/ - Utility programs. git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@487 fefdeb5f-60dc-0310-8127-8f9354f1896f
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1061
-rw-r--r--src/client.conf.in39
-rw-r--r--src/daemon.conf.in77
-rw-r--r--src/daemon/caps.c131
-rw-r--r--src/daemon/caps.h29
-rw-r--r--src/daemon/cmdline.c300
-rw-r--r--src/daemon/cmdline.h35
-rw-r--r--src/daemon/cpulimit.c236
-rw-r--r--src/daemon/cpulimit.h34
-rw-r--r--src/daemon/daemon-conf.c297
-rw-r--r--src/daemon/daemon-conf.h80
-rw-r--r--src/daemon/dumpmodules.c99
-rw-r--r--src/daemon/dumpmodules.h31
-rw-r--r--src/daemon/main.c472
-rwxr-xr-xsrc/default.pa.in66
-rw-r--r--src/default.pa.win3243
-rwxr-xr-xsrc/depmod.py73
-rw-r--r--src/modules/module-alsa-sink.c292
-rw-r--r--src/modules/module-alsa-source.c278
-rw-r--r--src/modules/module-cli.c88
-rw-r--r--src/modules/module-combine.c394
-rw-r--r--src/modules/module-defs.h.m429
-rw-r--r--src/modules/module-detect.c227
-rw-r--r--src/modules/module-esound-compat-spawnfd.c81
-rw-r--r--src/modules/module-esound-compat-spawnpid.c79
-rw-r--r--src/modules/module-esound-sink.c427
-rw-r--r--src/modules/module-lirc.c241
-rw-r--r--src/modules/module-match.c235
-rw-r--r--src/modules/module-mmkbd-evdev.c250
-rw-r--r--src/modules/module-native-protocol-fd.c85
-rw-r--r--src/modules/module-null-sink.c150
-rw-r--r--src/modules/module-oss-mmap.c426
-rw-r--r--src/modules/module-oss.c468
-rw-r--r--src/modules/module-pipe-sink.c237
-rw-r--r--src/modules/module-pipe-source.c210
-rw-r--r--src/modules/module-protocol-stub.c261
-rw-r--r--src/modules/module-sine.c182
-rw-r--r--src/modules/module-solaris.c488
-rw-r--r--src/modules/module-tunnel.c688
-rw-r--r--src/modules/module-waveout.c583
-rw-r--r--src/modules/module-x11-bell.c173
-rw-r--r--src/modules/module-x11-publish.c192
-rw-r--r--src/modules/module-zeroconf-publish.c508
-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
-rw-r--r--src/polypcore/alsa-util.c124
-rw-r--r--src/polypcore/alsa-util.h35
-rw-r--r--src/polypcore/authkey-prop.c87
-rw-r--r--src/polypcore/authkey-prop.h43
-rw-r--r--src/polypcore/authkey.c201
-rw-r--r--src/polypcore/authkey.h32
-rw-r--r--src/polypcore/autoload.c180
-rw-r--r--src/polypcore/autoload.h58
-rw-r--r--src/polypcore/cli-command.c834
-rw-r--r--src/polypcore/cli-command.h40
-rw-r--r--src/polypcore/cli-text.c385
-rw-r--r--src/polypcore/cli-text.h42
-rw-r--r--src/polypcore/cli.c147
-rw-r--r--src/polypcore/cli.h38
-rw-r--r--src/polypcore/client.c91
-rw-r--r--src/polypcore/client.h57
-rw-r--r--src/polypcore/conf-parser.c176
-rw-r--r--src/polypcore/conf-parser.h47
-rw-r--r--src/polypcore/core.c157
-rw-r--r--src/polypcore/core.h82
-rw-r--r--src/polypcore/dllmain.c46
-rw-r--r--src/polypcore/dynarray.c102
-rw-r--r--src/polypcore/dynarray.h49
-rw-r--r--src/polypcore/endianmacros.h77
-rw-r--r--src/polypcore/esound.h209
-rw-r--r--src/polypcore/g711.c2531
-rw-r--r--src/polypcore/g711.h40
-rw-r--r--src/polypcore/gccmacro.h53
-rw-r--r--src/polypcore/hashmap.c194
-rw-r--r--src/polypcore/hashmap.h53
-rw-r--r--src/polypcore/howl-wrap.c116
-rw-r--r--src/polypcore/howl-wrap.h37
-rw-r--r--src/polypcore/idxset.c390
-rw-r--r--src/polypcore/idxset.h94
-rw-r--r--src/polypcore/inet_ntop.c80
-rw-r--r--src/polypcore/inet_ntop.h12
-rw-r--r--src/polypcore/iochannel.c292
-rw-r--r--src/polypcore/iochannel.h72
-rw-r--r--src/polypcore/ioline.c368
-rw-r--r--src/polypcore/ioline.h51
-rw-r--r--src/polypcore/llist.h69
-rw-r--r--src/polypcore/log.c150
-rw-r--r--src/polypcore/log.h69
-rw-r--r--src/polypcore/mcalign.c188
-rw-r--r--src/polypcore/mcalign.h77
-rw-r--r--src/polypcore/memblock.c169
-rw-r--r--src/polypcore/memblock.h87
-rw-r--r--src/polypcore/memblockq.c343
-rw-r--r--src/polypcore/memblockq.h104
-rw-r--r--src/polypcore/memchunk.c58
-rw-r--r--src/polypcore/memchunk.h45
-rw-r--r--src/polypcore/modargs.c260
-rw-r--r--src/polypcore/modargs.h49
-rw-r--r--src/polypcore/modinfo.c84
-rw-r--r--src/polypcore/modinfo.h43
-rw-r--r--src/polypcore/module.c275
-rw-r--r--src/polypcore/module.h70
-rw-r--r--src/polypcore/namereg.c212
-rw-r--r--src/polypcore/namereg.h43
-rw-r--r--src/polypcore/native-common.h114
-rw-r--r--src/polypcore/oss-util.c167
-rw-r--r--src/polypcore/oss-util.h32
-rw-r--r--src/polypcore/packet.c69
-rw-r--r--src/polypcore/packet.h41
-rw-r--r--src/polypcore/parseaddr.c112
-rw-r--r--src/polypcore/parseaddr.h42
-rw-r--r--src/polypcore/pdispatch.c295
-rw-r--r--src/polypcore/pdispatch.h50
-rw-r--r--src/polypcore/pid.c286
-rw-r--r--src/polypcore/pid.h30
-rw-r--r--src/polypcore/play-memchunk.c118
-rw-r--r--src/polypcore/play-memchunk.h36
-rw-r--r--src/polypcore/poll.c190
-rw-r--r--src/polypcore/poll.h57
-rw-r--r--src/polypcore/props.c119
-rw-r--r--src/polypcore/props.h58
-rw-r--r--src/polypcore/protocol-cli.c95
-rw-r--r--src/polypcore/protocol-cli.h35
-rw-r--r--src/polypcore/protocol-esound.c1166
-rw-r--r--src/polypcore/protocol-esound.h35
-rw-r--r--src/polypcore/protocol-http.c265
-rw-r--r--src/polypcore/protocol-http.h35
-rw-r--r--src/polypcore/protocol-native.c2216
-rw-r--r--src/polypcore/protocol-native.h37
-rw-r--r--src/polypcore/protocol-simple.c453
-rw-r--r--src/polypcore/protocol-simple.h35
-rw-r--r--src/polypcore/pstream-util.c61
-rw-r--r--src/polypcore/pstream-util.h35
-rw-r--r--src/polypcore/pstream.c492
-rw-r--r--src/polypcore/pstream.h52
-rw-r--r--src/polypcore/queue.c108
-rw-r--r--src/polypcore/queue.h40
-rw-r--r--src/polypcore/random.c71
-rw-r--r--src/polypcore/random.h27
-rw-r--r--src/polypcore/resampler.c614
-rw-r--r--src/polypcore/resampler.h73
-rw-r--r--src/polypcore/sample-util.c328
-rw-r--r--src/polypcore/sample-util.h53
-rw-r--r--src/polypcore/scache.c392
-rw-r--r--src/polypcore/scache.h62
-rw-r--r--src/polypcore/sconv-s16be.c41
-rw-r--r--src/polypcore/sconv-s16be.h28
-rw-r--r--src/polypcore/sconv-s16le.c101
-rw-r--r--src/polypcore/sconv-s16le.h28
-rw-r--r--src/polypcore/sconv.c168
-rw-r--r--src/polypcore/sconv.h33
-rw-r--r--src/polypcore/sink-input.c368
-rw-r--r--src/polypcore/sink-input.h106
-rw-r--r--src/polypcore/sink.c445
-rw-r--r--src/polypcore/sink.h100
-rw-r--r--src/polypcore/sioman.c42
-rw-r--r--src/polypcore/sioman.h28
-rw-r--r--src/polypcore/socket-client.c513
-rw-r--r--src/polypcore/socket-client.h47
-rw-r--r--src/polypcore/socket-server.c432
-rw-r--r--src/polypcore/socket-server.h45
-rw-r--r--src/polypcore/socket-util.c246
-rw-r--r--src/polypcore/socket-util.h38
-rw-r--r--src/polypcore/sound-file-stream.c179
-rw-r--r--src/polypcore/sound-file-stream.h29
-rw-r--r--src/polypcore/sound-file.c139
-rw-r--r--src/polypcore/sound-file.h32
-rw-r--r--src/polypcore/source-output.c228
-rw-r--r--src/polypcore/source-output.h92
-rw-r--r--src/polypcore/source.c195
-rw-r--r--src/polypcore/source.h86
-rw-r--r--src/polypcore/strbuf.c167
-rw-r--r--src/polypcore/strbuf.h38
-rw-r--r--src/polypcore/strlist.c137
-rw-r--r--src/polypcore/strlist.h47
-rw-r--r--src/polypcore/subscribe.c230
-rw-r--r--src/polypcore/subscribe.h37
-rw-r--r--src/polypcore/tagstruct.c609
-rw-r--r--src/polypcore/tagstruct.h92
-rw-r--r--src/polypcore/tokenizer.c88
-rw-r--r--src/polypcore/tokenizer.h32
-rw-r--r--src/polypcore/util.c1176
-rw-r--r--src/polypcore/util.h99
-rw-r--r--src/polypcore/winsock.h23
-rw-r--r--src/polypcore/x11prop.c70
-rw-r--r--src/polypcore/x11prop.h33
-rw-r--r--src/polypcore/x11wrap.c235
-rw-r--r--src/polypcore/x11wrap.h52
-rw-r--r--src/polypcore/xmalloc.c122
-rw-r--r--src/polypcore/xmalloc.h58
-rw-r--r--src/tests/cpulimit-test.c92
-rw-r--r--src/tests/mainloop-test.c146
-rw-r--r--src/tests/mcalign-test.c96
-rw-r--r--src/tests/pacat-simple.c101
-rw-r--r--src/tests/parec-simple.c100
-rw-r--r--src/tests/strlist-test.c42
-rw-r--r--src/tests/voltest.c20
-rwxr-xr-xsrc/utils/esdcompat.sh.in98
-rw-r--r--src/utils/pabrowse.c139
-rw-r--r--src/utils/pacat.c541
-rw-r--r--src/utils/pacmd.c183
-rw-r--r--src/utils/pactl.c784
-rw-r--r--src/utils/paplay.c386
-rw-r--r--src/utils/pax11publish.c217
244 files changed, 48095 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..92c7dae1
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,1061 @@
+# $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.
+
+
+###################################
+# Extra directories #
+###################################
+
+polypincludedir=$(includedir)/polyp
+polypcoreincludedir=$(includedir)/polypcore
+polypconfdir=$(sysconfdir)/polypaudio
+
+modlibdir=$(libdir)/polypaudio
+
+###################################
+# Defines #
+###################################
+
+POLYPAUDIO_BINARY=$(bindir)/polypaudio$(EXEEXT)
+if OS_IS_WIN32
+DEFAULT_CONFIG_DIR=%POLYP_ROOT%
+else
+DEFAULT_CONFIG_DIR=$(polypconfdir)
+endif
+
+###################################
+# Compiler/linker flags #
+###################################
+
+AM_CFLAGS = -I$(top_srcdir)/src
+AM_CFLAGS += $(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS
+AM_CFLAGS += $(LTDLINCL)
+AM_CFLAGS += $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS)
+AM_CFLAGS += -DDLSEARCHPATH=\"$(modlibdir)\"
+#AM_CFLAGS += -DDLSEARCHPATH=\"$(shell pwd)\"
+AM_CFLAGS += -DDEFAULT_CONFIG_DIR=\"$(DEFAULT_CONFIG_DIR)\"
+AM_CFLAGS += -DPOLYPAUDIO_BINARY=\"$(POLYPAUDIO_BINARY)\"
+
+# This cool debug trap works on i386/gcc only
+AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")'
+
+AM_LIBADD = $(PTHREAD_LIBS)
+AM_LDADD = $(PTHREAD_LIBS)
+
+# Only required on some platforms but defined for all to avoid errors
+AM_LDFLAGS = -no-undefined
+
+if STATIC_BINS
+BINLDFLAGS = -static
+endif
+
+if OS_IS_WIN32
+AM_LDFLAGS+=-Wl,--export-all-symbols
+WINSOCK_LIBS=-lwsock32 -lws2_32 -lwininet
+endif
+
+###################################
+# Extra files #
+###################################
+
+EXTRA_DIST = \
+ client.conf.in \
+ daemon.conf.in \
+ default.pa.in \
+ depmod.py \
+ utils/esdcompat.sh.in \
+ modules/module-defs.h.m4
+
+polypconf_DATA = default.pa daemon.conf client.conf
+
+BUILT_SOURCES = polyp/polyplib-version.h
+
+###################################
+# Main daemon #
+###################################
+
+bin_PROGRAMS = polypaudio
+
+polypaudio_SOURCES = \
+ daemon/caps.h daemon/caps.c \
+ daemon/cmdline.c daemon/cmdline.h \
+ daemon/cpulimit.c daemon/cpulimit.h \
+ daemon/daemon-conf.c daemon/daemon-conf.h \
+ daemon/dumpmodules.c daemon/dumpmodules.h \
+ daemon/main.c \
+ polypcore/gccmacro.h
+
+polypaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+polypaudio_CPPFLAGS = $(AM_CPPFLAGS)
+polypaudio_LDADD = $(AM_LDADD) libpolypcore.la $(LIBLTDL) \
+ $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS)
+polypaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f))
+
+if PREOPEN_MODS
+PREOPEN_LIBS = $(PREOPEN_MODS)
+else
+PREOPEN_LIBS = $(modlib_LTLIBRARIES)
+endif
+
+###################################
+# Utility programs #
+###################################
+
+bin_PROGRAMS += \
+ pacat \
+ pactl \
+ paplay
+
+if HAVE_AF_UNIX
+bin_PROGRAMS += pacmd
+endif
+
+if HAVE_X11
+bin_PROGRAMS += pax11publish
+endif
+
+if HAVE_HOWL
+bin_PROGRAMS += pabrowse
+endif
+
+bin_SCRIPTS = utils/esdcompat.sh
+
+pacat_SOURCES = utils/pacat.c
+pacat_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la
+pacat_CFLAGS = $(AM_CFLAGS)
+pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+paplay_SOURCES = utils/paplay.c
+paplay_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la $(LIBSNDFILE_LIBS)
+paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
+paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pactl_SOURCES = utils/pactl.c
+pactl_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la $(LIBSNDFILE_LIBS)
+pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
+pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pacmd_SOURCES = utils/pacmd.c polypcore/pid.c polypcore/pid.h
+pacmd_CFLAGS = $(AM_CFLAGS)
+pacmd_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la
+pacmd_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pax11publish_SOURCES = utils/pax11publish.c
+pax11publish_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+pax11publish_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pabrowse_SOURCES = utils/pabrowse.c
+pabrowse_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la libpolyp-browse-@PA_MAJORMINOR@.la
+pabrowse_CFLAGS = $(AM_CFLAGS)
+pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+###################################
+# Test programs #
+###################################
+
+noinst_PROGRAMS = \
+ mainloop-test \
+ mcalign-test \
+ pacat-simple \
+ parec-simple \
+ strlist-test \
+ voltest
+
+if HAVE_SIGXCPU
+noinst_PROGRAMS += \
+ cpulimit-test \
+ cpulimit-test2
+endif
+
+if HAVE_GLIB20
+noinst_PROGRAMS += \
+ mainloop-test-glib
+endif
+
+if HAVE_GLIB12
+noinst_PROGRAMS += \
+ mainloop-test-glib12
+endif
+
+mainloop_test_SOURCES = tests/mainloop-test.c
+mainloop_test_CFLAGS = $(AM_CFLAGS)
+mainloop_test_LDADD = $(AM_LDADD) libpolyp-mainloop-@PA_MAJORMINOR@.la libpolyp-@PA_MAJORMINOR@.la
+mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+mcalign_test_SOURCES = tests/mcalign-test.c
+mcalign_test_CFLAGS = $(AM_CFLAGS)
+mcalign_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpolypcore.la
+mcalign_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pacat_simple_SOURCES = tests/pacat-simple.c
+pacat_simple_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-simple-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la
+pacat_simple_CFLAGS = $(AM_CFLAGS)
+pacat_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+parec_simple_SOURCES = tests/parec-simple.c
+parec_simple_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-simple-@PA_MAJORMINOR@.la libpolyp-error-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la
+parec_simple_CFLAGS = $(AM_CFLAGS)
+parec_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+strlist_test_SOURCES = tests/strlist-test.c
+strlist_test_CFLAGS = $(AM_CFLAGS)
+strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpolypcore.la libstrlist.la
+strlist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+voltest_SOURCES = tests/voltest.c
+voltest_CFLAGS = $(AM_CFLAGS)
+voltest_LDADD = $(AM_LDADD) libpolyp-@PA_MAJORMINOR@.la
+voltest_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+cpulimit_test_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h
+cpulimit_test_CFLAGS = $(AM_CFLAGS)
+cpulimit_test_LDADD = $(AM_LDADD) libpolyp-mainloop-@PA_MAJORMINOR@.la libpolypcore.la
+cpulimit_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+cpulimit_test2_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h
+cpulimit_test2_CFLAGS = $(AM_CFLAGS) -DTEST2
+cpulimit_test2_LDADD = $(AM_LDADD) libpolyp-mainloop-@PA_MAJORMINOR@.la libpolypcore.la
+cpulimit_test2_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+mainloop_test_glib_SOURCES = $(mainloop_test_SOURCES)
+mainloop_test_glib_CFLAGS = $(mainloop_test_CFLAGS) $(GLIB20_CFLAGS) -DGLIB_MAIN_LOOP
+mainloop_test_glib_LDADD = $(mainloop_test_LDADD) $(GLIB20_LIBS) libpolyp-mainloop-glib-@PA_MAJORMINOR@.la
+mainloop_test_glib_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+mainloop_test_glib12_SOURCES = $(mainloop_test_SOURCES)
+mainloop_test_glib12_CFLAGS = $(mainloop_test_CFLAGS) $(GLIB12_CFLAGS) -DGLIB_MAIN_LOOP
+mainloop_test_glib12_LDADD = $(mainloop_test_LDADD) $(GLIB12_LIBS) libpolyp-mainloop-glib12-@PA_MAJORMINOR@.la
+mainloop_test_glib12_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+###################################
+# Client library #
+###################################
+
+polypinclude_HEADERS = \
+ polyp/cdecl.h \
+ polyp/channelmap.h \
+ polyp/glib-mainloop.h \
+ polyp/mainloop.h \
+ polyp/mainloop-api.h \
+ polyp/mainloop-signal.h \
+ polyp/polyplib.h \
+ polyp/polyplib-context.h \
+ polyp/polyplib-def.h \
+ polyp/polyplib-error.h \
+ polyp/polyplib-introspect.h \
+ polyp/polyplib-operation.h \
+ polyp/polyplib-scache.h \
+ polyp/polyplib-simple.h \
+ polyp/polyplib-stream.h \
+ polyp/polyplib-subscribe.h \
+ polyp/polyplib-version.h \
+ polyp/sample.h \
+ polyp/volume.h
+
+if HAVE_HOWL
+polypinclude_HEADERS += \
+ polyp/polyplib-browser.h
+endif
+
+lib_LTLIBRARIES = \
+ libpolyp-@PA_MAJORMINOR@.la \
+ libpolyp-error-@PA_MAJORMINOR@.la \
+ libpolyp-mainloop-@PA_MAJORMINOR@.la \
+ libpolyp-simple-@PA_MAJORMINOR@.la
+
+if HAVE_HOWL
+lib_LTLIBRARIES += \
+ libpolyp-browse-@PA_MAJORMINOR@.la
+endif
+
+if HAVE_GLIB20
+lib_LTLIBRARIES += \
+ libpolyp-mainloop-glib-@PA_MAJORMINOR@.la
+endif
+
+if HAVE_GLIB12
+lib_LTLIBRARIES += \
+ libpolyp-mainloop-glib12-@PA_MAJORMINOR@.la
+endif
+
+# Public interface
+libpolyp_@PA_MAJORMINOR@_la_SOURCES = \
+ polyp/cdecl.h \
+ polyp/channelmap.c polyp/channelmap.h \
+ polyp/client-conf.c polyp/client-conf.h \
+ polyp/llist.h \
+ polyp/mainloop-api.c polyp/mainloop-api.h \
+ polyp/polyplib.h \
+ polyp/polyplib-context.c polyp/polyplib-context.h \
+ polyp/polyplib-def.h \
+ polyp/polyplib-internal.h \
+ polyp/polyplib-introspect.c polyp/polyplib-introspect.h \
+ polyp/polyplib-operation.c polyp/polyplib-operation.h \
+ polyp/polyplib-scache.c polyp/polyplib-scache.h \
+ polyp/polyplib-stream.c polyp/polyplib-stream.h \
+ polyp/polyplib-subscribe.c polyp/polyplib-subscribe.h \
+ polyp/sample.c polyp/sample.h \
+ polyp/volume.c polyp/volume.h
+
+# Internal stuff that is shared with libpolypcore
+libpolyp_@PA_MAJORMINOR@_la_SOURCES += \
+ polypcore/authkey.c polypcore/authkey.h \
+ polypcore/conf-parser.c polypcore/conf-parser.h \
+ polypcore/dllmain.c \
+ polypcore/dynarray.c polypcore/dynarray.h \
+ polypcore/gccmacro.h \
+ polypcore/idxset.c polypcore/idxset.h \
+ polypcore/iochannel.c polypcore/iochannel.h \
+ polypcore/log.c polypcore/log.h \
+ polypcore/mcalign.c polypcore/mcalign.h \
+ polypcore/memblock.c polypcore/memblock.h \
+ polypcore/memchunk.c polypcore/memchunk.h \
+ polypcore/native-common.h \
+ polypcore/packet.c polypcore/packet.h \
+ polypcore/parseaddr.c polypcore/parseaddr.h \
+ polypcore/pdispatch.c polypcore/pdispatch.h \
+ polypcore/pstream.c polypcore/pstream.h \
+ polypcore/pstream-util.c polypcore/pstream-util.h \
+ polypcore/queue.c polypcore/queue.h \
+ polypcore/random.c polypcore/random.h \
+ polypcore/socket-client.c polypcore/socket-client.h \
+ polypcore/socket-util.c polypcore/socket-util.h \
+ polypcore/strbuf.c polypcore/strbuf.h \
+ polypcore/strlist.c polypcore/strlist.h \
+ polypcore/tagstruct.c polypcore/tagstruct.h \
+ polypcore/util.c polypcore/util.h \
+ polypcore/winsock.h \
+ polypcore/xmalloc.c polypcore/xmalloc.h
+
+if HAVE_X11
+libpolyp_@PA_MAJORMINOR@_la_SOURCES += \
+ polyp/client-conf-x11.c polyp/client-conf-x11.h \
+ polypcore/x11prop.c polypcore/x11prop.h
+endif
+
+libpolyp_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS)
+libpolyp_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+libpolyp_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS)
+
+if HAVE_X11
+libpolyp_@PA_MAJORMINOR@_la_CFLAGS += $(X_CFLAGS)
+libpolyp_@PA_MAJORMINOR@_la_LDFLAGS += $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+endif
+
+if HAVE_LIBASYNCNS
+libpolyp_@PA_MAJORMINOR@_la_CFLAGS += $(LIBASYNCNS_CFLAGS)
+libpolyp_@PA_MAJORMINOR@_la_LIBADD += $(LIBASYNCNS_LIBS)
+endif
+
+libpolyp_error_@PA_MAJORMINOR@_la_SOURCES = polyp/polyplib-error.c polyp/polyplib-error.h
+libpolyp_error_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS)
+libpolyp_error_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-@PA_MAJORMINOR@.la
+libpolyp_error_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+libpolyp_mainloop_@PA_MAJORMINOR@_la_SOURCES = \
+ polyp/mainloop.c polyp/mainloop.h \
+ polyp/mainloop-signal.c polyp/mainloop-signal.h \
+ polypcore/poll.c polypcore/poll.h
+libpolyp_mainloop_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS)
+libpolyp_mainloop_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-@PA_MAJORMINOR@.la $(WINSOCK_LIBS)
+libpolyp_mainloop_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+libpolyp_simple_@PA_MAJORMINOR@_la_SOURCES = polyp/polyplib-simple.c polyp/polyplib-simple.h
+libpolyp_simple_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS)
+libpolyp_simple_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-@PA_MAJORMINOR@.la libpolyp-mainloop-@PA_MAJORMINOR@.la
+libpolyp_simple_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+libpolyp_browse_@PA_MAJORMINOR@_la_SOURCES = polyp/polyplib-browser.c polyp/polyplib-browser.h
+libpolyp_browse_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(HOWL_CFLAGS)
+libpolyp_browse_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-@PA_MAJORMINOR@.la $(HOWL_LIBS)
+libpolyp_browse_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+libpolyp_mainloop_glib_@PA_MAJORMINOR@_la_SOURCES = polyp/glib-mainloop.h polyp/glib-mainloop.c
+libpolyp_mainloop_glib_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(GLIB20_CFLAGS)
+libpolyp_mainloop_glib_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-mainloop-@PA_MAJORMINOR@.la $(GLIB20_LIBS)
+libpolyp_mainloop_glib_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+libpolyp_mainloop_glib12_@PA_MAJORMINOR@_la_SOURCES = polyp/glib-mainloop.h polyp/glib12-mainloop.c
+libpolyp_mainloop_glib12_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(GLIB12_CFLAGS)
+libpolyp_mainloop_glib12_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) libpolyp-mainloop-@PA_MAJORMINOR@.la $(GLIB12_LIBS)
+libpolyp_mainloop_glib12_@PA_MAJORMINOR@_la_LDFLAGS = -version-info 0:0:0
+
+###################################
+# Daemon core library #
+###################################
+
+polypcoreinclude_HEADERS = \
+ polypcore/cli-command.h \
+ polypcore/client.h \
+ polypcore/core.h \
+ polypcore/dynarray.h \
+ polypcore/endianmacros.h \
+ polypcore/hashmap.h \
+ polypcore/idxset.h \
+ polypcore/iochannel.h \
+ polypcore/log.h \
+ polypcore/memblock.h \
+ polypcore/memblockq.h \
+ polypcore/memchunk.h \
+ polypcore/modargs.h \
+ polypcore/module.h \
+ polypcore/namereg.h \
+ polypcore/queue.h \
+ polypcore/resampler.h \
+ polypcore/sample-util.h \
+ polypcore/sink.h \
+ polypcore/sink-input.h \
+ polypcore/sioman.h \
+ polypcore/socket-server.h \
+ polypcore/socket-client.h \
+ polypcore/socket-util.h \
+ polypcore/source.h \
+ polypcore/source-output.h \
+ polypcore/strbuf.h \
+ polypcore/tokenizer.h \
+ polypcore/tagstruct.h \
+ polypcore/util.h
+
+lib_LTLIBRARIES += libpolypcore.la
+
+# Some public stuff is used even in the core.
+libpolypcore_la_SOURCES = \
+ polyp/channelmap.c polyp/channelmap.h \
+ polyp/mainloop.c polyp/mainloop.h \
+ polyp/mainloop-api.c polyp/mainloop-api.h \
+ polyp/mainloop-signal.c polyp/mainloop-signal.h \
+ polyp/sample.c polyp/sample.h \
+ polyp/volume.c polyp/volume.h
+
+# Pure core stuff (some are shared in libpolyp though).
+libpolypcore_la_SOURCES += \
+ polypcore/autoload.c polypcore/autoload.h \
+ polypcore/cli-command.c polypcore/cli-command.h \
+ polypcore/cli-text.c polypcore/cli-text.h \
+ polypcore/client.c polypcore/client.h \
+ polypcore/conf-parser.c polypcore/conf-parser.h \
+ polypcore/core.c polypcore/core.h \
+ polypcore/dllmain.c \
+ polypcore/dynarray.c polypcore/dynarray.h \
+ polypcore/endianmacros.h \
+ polypcore/g711.c polypcore/g711.h \
+ polypcore/hashmap.c polypcore/hashmap.h \
+ polypcore/idxset.c polypcore/idxset.h \
+ polypcore/log.c polypcore/log.h \
+ polypcore/mcalign.c polypcore/mcalign.h \
+ polypcore/memblock.c polypcore/memblock.h \
+ polypcore/memblockq.c polypcore/memblockq.h \
+ polypcore/memchunk.c polypcore/memchunk.h \
+ polypcore/modargs.c polypcore/modargs.h \
+ polypcore/modinfo.c polypcore/modinfo.h \
+ polypcore/module.c polypcore/module.h \
+ polypcore/namereg.c polypcore/namereg.h \
+ polypcore/pid.c polypcore/pid.h \
+ polypcore/play-memchunk.c polypcore/play-memchunk.h \
+ polypcore/poll.c polypcore/poll.h \
+ polypcore/props.c polypcore/props.h \
+ polypcore/queue.c polypcore/queue.h \
+ polypcore/random.c polypcore/random.h \
+ polypcore/resampler.c polypcore/resampler.h \
+ polypcore/sample-util.c polypcore/sample-util.h \
+ polypcore/scache.c polypcore/scache.h \
+ polypcore/sconv.c polypcore/sconv.h \
+ polypcore/sconv-s16be.c polypcore/sconv-s16be.h \
+ polypcore/sconv-s16le.c polypcore/sconv-s16le.h \
+ polypcore/sink.c polypcore/sink.h \
+ polypcore/sink-input.c polypcore/sink-input.h \
+ polypcore/sioman.c polypcore/sioman.h \
+ polypcore/sound-file.c polypcore/sound-file.h \
+ polypcore/sound-file-stream.c polypcore/sound-file-stream.h \
+ polypcore/source.c polypcore/source.h \
+ polypcore/source-output.c polypcore/source-output.h \
+ polypcore/strbuf.c polypcore/strbuf.h \
+ polypcore/subscribe.c polypcore/subscribe.h \
+ polypcore/tokenizer.c polypcore/tokenizer.h \
+ polypcore/util.c polypcore/util.h \
+ polypcore/winsock.h \
+ polypcore/xmalloc.c polypcore/xmalloc.h
+
+libpolypcore_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBOIL_CFLAGS)
+libpolypcore_la_LDFLAGS = -avoid-version
+libpolypcore_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS)
+
+###################################
+# Plug-in support libraries #
+###################################
+
+### Warning! Due to an obscure bug in libtool/automake it is required
+### that the libraries in modlib_LTLIBRARIES are specified in-order,
+### i.e. libraries near the end of the list depend on libraries near
+### the head, and not the other way!
+
+modlib_LTLIBRARIES = \
+ libsocket-util.la \
+ libiochannel.la \
+ libsocket-server.la \
+ libsocket-client.la \
+ libparseaddr.la \
+ libpacket.la \
+ libpstream.la \
+ libioline.la \
+ libcli.la \
+ libprotocol-cli.la \
+ libtagstruct.la \
+ libpstream-util.la \
+ libpdispatch.la \
+ libauthkey.la \
+ libauthkey-prop.la \
+ libstrlist.la \
+ libprotocol-simple.la \
+ libprotocol-esound.la \
+ libprotocol-native.la \
+ libprotocol-http.la
+
+if HAVE_X11
+modlib_LTLIBRARIES += \
+ libx11wrap.la \
+ libx11prop.la
+endif
+
+if HAVE_OSS
+modlib_LTLIBRARIES += \
+ liboss-util.la
+endif
+
+if HAVE_ALSA
+modlib_LTLIBRARIES += \
+ libalsa-util.la
+endif
+
+if HAVE_HOWL
+modlib_LTLIBRARIES += \
+ libhowl-wrap.la
+endif
+
+libprotocol_simple_la_SOURCES = polypcore/protocol-simple.c polypcore/protocol-simple.h
+libprotocol_simple_la_LDFLAGS = -avoid-version
+libprotocol_simple_la_LIBADD = $(AM_LIBADD) libpolypcore.la libsocket-server.la libiochannel.la
+
+libsocket_server_la_SOURCES = \
+ polypcore/inet_ntop.c polypcore/inet_ntop.h \
+ polypcore/socket-server.c polypcore/socket-server.h
+libsocket_server_la_LDFLAGS = -avoid-version
+libsocket_server_la_LIBADD = $(AM_LIBADD) libpolypcore.la libiochannel.la libsocket-util.la $(LIBWRAP_LIBS) $(WINSOCK_LIBS)
+
+libsocket_client_la_SOURCES = polypcore/socket-client.c polypcore/socket-client.h
+libsocket_client_la_LDFLAGS = -avoid-version
+libsocket_client_la_LIBADD = $(AM_LIBADD) libpolypcore.la libiochannel.la libsocket-util.la libparseaddr.la $(LIBASYNCNS_LIBS) $(WINSOCK_LIBS)
+libsocket_client_la_CFLAGS = $(AM_CFLAGS) $(LIBASYNCNS_CFLAGS)
+
+libparseaddr_la_SOURCES = polypcore/parseaddr.c polypcore/parseaddr.h
+libparseaddr_la_LDFLAGS = -avoid-version
+libparseaddr_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+libpstream_la_SOURCES = polypcore/pstream.c polypcore/pstream.h
+libpstream_la_LDFLAGS = -avoid-version
+libpstream_la_LIBADD = $(AM_LIBADD) libpolypcore.la libpacket.la libiochannel.la $(WINSOCK_LIBS)
+
+libpstream_util_la_SOURCES = polypcore/pstream-util.c polypcore/pstream-util.h
+libpstream_util_la_LDFLAGS = -avoid-version
+libpstream_util_la_LIBADD = $(AM_LIBADD) libpacket.la libpstream.la libtagstruct.la
+
+libpdispatch_la_SOURCES = polypcore/pdispatch.c polypcore/pdispatch.h
+libpdispatch_la_LDFLAGS = -avoid-version
+libpdispatch_la_LIBADD = $(AM_LIBADD) libtagstruct.la libpolypcore.la
+
+libiochannel_la_SOURCES = polypcore/iochannel.c polypcore/iochannel.h
+libiochannel_la_LDFLAGS = -avoid-version
+libiochannel_la_LIBADD = $(AM_LIBADD) libsocket-util.la libpolypcore.la $(WINSOCK_LIBS)
+
+libpacket_la_SOURCES = polypcore/packet.c polypcore/packet.h
+libpacket_la_LDFLAGS = -avoid-version
+libpacket_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+libioline_la_SOURCES = polypcore/ioline.c polypcore/ioline.h
+libioline_la_LDFLAGS = -avoid-version
+libioline_la_LIBADD = $(AM_LIBADD) libiochannel.la libpolypcore.la
+
+libcli_la_SOURCES = polypcore/cli.c polypcore/cli.h
+libcli_la_CPPFLAGS = $(AM_CPPFLAGS)
+libcli_la_LDFLAGS = -avoid-version
+libcli_la_LIBADD = $(AM_LIBADD) libiochannel.la libioline.la libpolypcore.la
+
+libstrlist_la_SOURCES = polypcore/strlist.c polypcore/strlist.h
+libstrlist_la_LDFLAGS = -avoid-version
+libstrlist_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+libprotocol_cli_la_SOURCES = polypcore/protocol-cli.c polypcore/protocol-cli.h
+libprotocol_cli_la_LDFLAGS = -avoid-version
+libprotocol_cli_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libcli.la libpolypcore.la
+
+libprotocol_http_la_SOURCES = polypcore/protocol-http.c polypcore/protocol-http.h
+libprotocol_http_la_LDFLAGS = -avoid-version
+libprotocol_http_la_LIBADD = $(AM_LIBADD) libsocket-server.la libioline.la libpolypcore.la libiochannel.la
+
+libprotocol_native_la_SOURCES = polypcore/protocol-native.c polypcore/protocol-native.h polypcore/native-common.h
+libprotocol_native_la_LDFLAGS = -avoid-version
+libprotocol_native_la_LIBADD = $(AM_LIBADD) libsocket-server.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libstrlist.la libpolypcore.la libiochannel.la
+
+libtagstruct_la_SOURCES = polypcore/tagstruct.c polypcore/tagstruct.h
+libtagstruct_la_LDFLAGS = -avoid-version
+libtagstruct_la_LIBADD = $(AM_LIBADD) libpolypcore.la $(WINSOCK_LIBS)
+
+libprotocol_esound_la_SOURCES = polypcore/protocol-esound.c polypcore/protocol-esound.h polypcore/esound.h
+libprotocol_esound_la_LDFLAGS = -avoid-version
+libprotocol_esound_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libauthkey.la libpolypcore.la
+
+libauthkey_la_SOURCES = polypcore/authkey.c polypcore/authkey.h
+libauthkey_la_LDFLAGS = -avoid-version
+libauthkey_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+libauthkey_prop_la_SOURCES = polypcore/authkey-prop.c polypcore/authkey-prop.h
+libauthkey_prop_la_LDFLAGS = -avoid-version
+libauthkey_prop_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+libsocket_util_la_SOURCES = polypcore/socket-util.c polypcore/socket-util.h
+libsocket_util_la_LDFLAGS = -avoid-version
+libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS)
+
+# X11
+
+libx11wrap_la_SOURCES = polypcore/x11wrap.c polypcore/x11wrap.h
+libx11wrap_la_LDFLAGS = -avoid-version
+libx11wrap_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+libx11wrap_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+
+libx11prop_la_SOURCES = polypcore/x11prop.c polypcore/x11prop.h
+libx11prop_la_LDFLAGS = -avoid-version
+libx11prop_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+libx11prop_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+
+# OSS
+
+liboss_util_la_SOURCES = polypcore/oss-util.c polypcore/oss-util.h
+liboss_util_la_LDFLAGS = -avoid-version
+
+# ALSA
+
+libalsa_util_la_SOURCES = polypcore/alsa-util.c polypcore/alsa-util.h
+libalsa_util_la_LDFLAGS = -avoid-version
+libalsa_util_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS)
+libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+# HOWL
+
+libhowl_wrap_la_SOURCES = polypcore/howl-wrap.c polypcore/howl-wrap.h
+libhowl_wrap_la_LDFLAGS = -avoid-version
+libhowl_wrap_la_LIBADD = $(AM_LIBADD) $(HOWL_LIBS)
+libhowl_wrap_la_CFLAGS = $(AM_CFLAGS) $(HOWL_CFLAGS)
+
+###################################
+# Plug-in libraries #
+###################################
+
+modlib_LTLIBRARIES += \
+ module-cli.la \
+ module-cli-protocol-tcp.la \
+ module-cli-protocol-tcp6.la \
+ module-simple-protocol-tcp.la \
+ module-simple-protocol-tcp6.la \
+ module-esound-protocol-tcp.la \
+ module-esound-protocol-tcp6.la \
+ module-native-protocol-tcp.la \
+ module-native-protocol-tcp6.la \
+ module-native-protocol-fd.la \
+ module-sine.la \
+ module-combine.la \
+ module-tunnel-sink.la \
+ module-tunnel-source.la \
+ module-null-sink.la \
+ module-esound-sink.la \
+ module-http-protocol-tcp.la \
+ module-http-protocol-tcp6.la \
+ module-detect.la
+
+if HAVE_AF_UNIX
+modlib_LTLIBRARIES += \
+ module-cli-protocol-unix.la \
+ module-simple-protocol-unix.la \
+ module-esound-protocol-unix.la \
+ module-native-protocol-unix.la \
+ module-http-protocol-unix.la
+endif
+
+if HAVE_MKFIFO
+modlib_LTLIBRARIES += \
+ module-pipe-sink.la \
+ module-pipe-source.la
+endif
+
+if !OS_IS_WIN32
+modlib_LTLIBRARIES += \
+ module-esound-compat-spawnfd.la \
+ module-esound-compat-spawnpid.la
+endif
+
+if HAVE_REGEX
+modlib_LTLIBRARIES += \
+ module-match.la
+endif
+
+if HAVE_X11
+modlib_LTLIBRARIES += \
+ module-x11-bell.la \
+ module-x11-publish.la
+endif
+
+if HAVE_OSS
+modlib_LTLIBRARIES += \
+ module-oss.la \
+ module-oss-mmap.la
+endif
+
+if HAVE_ALSA
+modlib_LTLIBRARIES += \
+ module-alsa-sink.la \
+ module-alsa-source.la
+endif
+
+if HAVE_SOLARIS
+modlib_LTLIBRARIES += \
+ module-solaris.la
+endif
+
+if HAVE_HOWL
+modlib_LTLIBRARIES += \
+ module-zeroconf-publish.la
+endif
+
+if HAVE_LIRC
+modlib_LTLIBRARIES += \
+ module-lirc.la
+endif
+
+if HAVE_EVDEV
+modlib_LTLIBRARIES += \
+ module-mmkbd-evdev.la
+endif
+
+if OS_IS_WIN32
+modlib_LTLIBRARIES += \
+ module-waveout.la
+endif
+
+# These are generated by a M4 script
+
+SYMDEF_FILES = \
+ modules/module-cli-symdef.h \
+ modules/module-cli-protocol-tcp-symdef.h \
+ modules/module-cli-protocol-tcp6-symdef.h \
+ modules/module-cli-protocol-unix-symdef.h \
+ modules/module-pipe-sink-symdef.h \
+ modules/module-pipe-source-symdef.h \
+ modules/module-simple-protocol-tcp-symdef.h \
+ modules/module-simple-protocol-tcp6-symdef.h \
+ modules/module-simple-protocol-unix-symdef.h \
+ modules/module-esound-protocol-tcp-symdef.h \
+ modules/module-esound-protocol-tcp6-symdef.h \
+ modules/module-esound-protocol-unix-symdef.h \
+ modules/module-native-protocol-tcp-symdef.h \
+ modules/module-native-protocol-tcp6-symdef.h \
+ modules/module-native-protocol-unix-symdef.h \
+ modules/module-native-protocol-fd-symdef.h \
+ modules/module-sine-symdef.h \
+ modules/module-combine-symdef.h \
+ modules/module-esound-compat-spawnfd-symdef.h \
+ modules/module-esound-compat-spawnpid-symdef.h \
+ modules/module-match-symdef.h \
+ modules/module-tunnel-sink-symdef.h \
+ modules/module-tunnel-source-symdef.h \
+ modules/module-null-sink-symdef.h \
+ modules/module-esound-sink-symdef.h \
+ modules/module-zeroconf-publish-symdef.h \
+ modules/module-lirc-symdef.h \
+ modules/module-mmkbd-evdev-symdef.h \
+ modules/module-http-protocol-tcp-symdef.h \
+ modules/module-http-protocol-tcp6-symdef.h \
+ modules/module-http-protocol-unix-symdef.h \
+ modules/module-x11-bell-symdef.h \
+ modules/module-x11-publish-symdef.h \
+ modules/module-oss-symdef.h \
+ modules/module-oss-mmap-symdef.h \
+ modules/module-alsa-sink-symdef.h \
+ modules/module-alsa-source-symdef.h \
+ modules/module-solaris-symdef.h \
+ modules/module-waveout-symdef.h \
+ modules/module-detect-symdef.h
+
+EXTRA_DIST += $(SYMDEF_FILES)
+BUILT_SOURCES += $(SYMDEF_FILES)
+
+$(SYMDEF_FILES): modules/module-defs.h.m4
+ $(M4) -Dfname="$@" $< > $@
+
+# Simple protocol
+
+module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_simple_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS)
+module_simple_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_simple_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-simple.la libsocket-server.la
+
+module_simple_protocol_tcp6_la_SOURCES = modules/module-protocol-stub.c
+module_simple_protocol_tcp6_la_CFLAGS = -DUSE_TCP6_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS)
+module_simple_protocol_tcp6_la_LDFLAGS = -module -avoid-version
+module_simple_protocol_tcp6_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-simple.la libsocket-server.la
+
+module_simple_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS)
+module_simple_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_simple_protocol_unix_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-simple.la libsocket-server.la libsocket-util.la
+
+# CLI protocol
+
+module_cli_la_SOURCES = modules/module-cli.c
+module_cli_la_LDFLAGS = -module -avoid-version
+module_cli_la_LIBADD = $(AM_LIBADD) libcli.la libiochannel.la libpolypcore.la
+
+module_cli_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_cli_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS)
+module_cli_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_cli_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-cli.la libsocket-server.la
+
+module_cli_protocol_tcp6_la_SOURCES = modules/module-protocol-stub.c
+module_cli_protocol_tcp6_la_CFLAGS = -DUSE_TCP6_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS)
+module_cli_protocol_tcp6_la_LDFLAGS = -module -avoid-version
+module_cli_protocol_tcp6_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-cli.la libsocket-server.la
+
+module_cli_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS)
+module_cli_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_cli_protocol_unix_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-cli.la libsocket-server.la libsocket-util.la
+
+# HTTP protocol
+
+module_http_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_http_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS)
+module_http_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_http_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-http.la libsocket-server.la
+
+module_http_protocol_tcp6_la_SOURCES = modules/module-protocol-stub.c
+module_http_protocol_tcp6_la_CFLAGS = -DUSE_TCP6_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS)
+module_http_protocol_tcp6_la_LDFLAGS = -module -avoid-version
+module_http_protocol_tcp6_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-http.la libsocket-server.la
+
+module_http_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_http_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS)
+module_http_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_http_protocol_unix_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-http.la libsocket-server.la libsocket-util.la
+
+# Native protocol
+
+module_native_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_native_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS)
+module_native_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_native_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-native.la libsocket-server.la
+
+module_native_protocol_tcp6_la_SOURCES = modules/module-protocol-stub.c
+module_native_protocol_tcp6_la_CFLAGS = -DUSE_TCP6_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS)
+module_native_protocol_tcp6_la_LDFLAGS = -module -avoid-version
+module_native_protocol_tcp6_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-native.la libsocket-server.la
+
+module_native_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_native_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS)
+module_native_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_native_protocol_unix_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-native.la libsocket-server.la libsocket-util.la
+
+module_native_protocol_fd_la_SOURCES = modules/module-native-protocol-fd.c
+module_native_protocol_fd_la_CFLAGS = $(AM_CFLAGS)
+module_native_protocol_fd_la_LDFLAGS = -module -avoid-version
+module_native_protocol_fd_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-native.la libsocket-server.la libsocket-util.la libiochannel.la
+
+# EsounD protocol
+
+module_esound_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_esound_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS)
+module_esound_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_esound_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-esound.la libsocket-server.la
+
+module_esound_protocol_tcp6_la_SOURCES = modules/module-protocol-stub.c
+module_esound_protocol_tcp6_la_CFLAGS = -DUSE_TCP6_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS)
+module_esound_protocol_tcp6_la_LDFLAGS = -module -avoid-version
+module_esound_protocol_tcp6_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-esound.la libsocket-server.la
+
+module_esound_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_esound_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS)
+module_esound_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_esound_protocol_unix_la_LIBADD = $(AM_LIBADD) libpolypcore.la libprotocol-esound.la libsocket-server.la libsocket-util.la
+
+module_esound_compat_spawnfd_la_SOURCES = modules/module-esound-compat-spawnfd.c
+module_esound_compat_spawnfd_la_LDFLAGS = -module -avoid-version
+module_esound_compat_spawnfd_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+module_esound_compat_spawnpid_la_SOURCES = modules/module-esound-compat-spawnpid.c
+module_esound_compat_spawnpid_la_LDFLAGS = -module -avoid-version
+module_esound_compat_spawnpid_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+module_esound_sink_la_SOURCES = modules/module-esound-sink.c
+module_esound_sink_la_LDFLAGS = -module -avoid-version
+module_esound_sink_la_LIBADD = $(AM_LIBADD) libpolypcore.la libiochannel.la libsocket-client.la libauthkey.la
+
+# Pipes
+
+module_pipe_sink_la_SOURCES = modules/module-pipe-sink.c
+module_pipe_sink_la_LDFLAGS = -module -avoid-version
+module_pipe_sink_la_LIBADD = $(AM_LIBADD) libpolypcore.la libiochannel.la
+
+module_pipe_source_la_SOURCES = modules/module-pipe-source.c
+module_pipe_source_la_LDFLAGS = -module -avoid-version
+module_pipe_source_la_LIBADD = $(AM_LIBADD) libpolypcore.la libiochannel.la
+
+# Fake sources/sinks
+
+module_sine_la_SOURCES = modules/module-sine.c
+module_sine_la_LDFLAGS = -module -avoid-version
+module_sine_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+module_null_sink_la_SOURCES = modules/module-null-sink.c
+module_null_sink_la_LDFLAGS = -module -avoid-version
+module_null_sink_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+# Couplings
+
+module_combine_la_SOURCES = modules/module-combine.c
+module_combine_la_LDFLAGS = -module -avoid-version
+module_combine_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+module_match_la_SOURCES = modules/module-match.c
+module_match_la_LDFLAGS = -module -avoid-version
+module_match_la_LIBADD = $(AM_LIBADD) libpolypcore.la
+
+module_tunnel_sink_la_SOURCES = modules/module-tunnel.c
+module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS)
+module_tunnel_sink_la_LDFLAGS = -module -avoid-version
+module_tunnel_sink_la_LIBADD = $(AM_LIBADD) libpolypcore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la
+
+module_tunnel_source_la_SOURCES = modules/module-tunnel.c
+module_tunnel_source_la_LDFLAGS = -module -avoid-version
+module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpolypcore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la
+
+# X11
+
+module_x11_bell_la_SOURCES = modules/module-x11-bell.c
+module_x11_bell_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+module_x11_bell_la_LDFLAGS = -module -avoid-version
+module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la
+
+module_x11_publish_la_SOURCES = modules/module-x11-publish.c
+module_x11_publish_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+module_x11_publish_la_LDFLAGS = -module -avoid-version
+module_x11_publish_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la libauthkey.la libauthkey-prop.la libx11prop.la libstrlist.la
+
+# OSS
+
+module_oss_la_SOURCES = modules/module-oss.c
+module_oss_la_LDFLAGS = -module -avoid-version
+module_oss_la_LIBADD = $(AM_LIBADD) libiochannel.la liboss-util.la
+
+module_oss_mmap_la_SOURCES = modules/module-oss-mmap.c
+module_oss_mmap_la_LDFLAGS = -module -avoid-version
+module_oss_mmap_la_LIBADD = $(AM_LIBADD) liboss-util.la
+
+# ALSA
+
+module_alsa_sink_la_SOURCES = modules/module-alsa-sink.c
+module_alsa_sink_la_LDFLAGS = -module -avoid-version
+module_alsa_sink_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la
+module_alsa_sink_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+module_alsa_source_la_SOURCES = modules/module-alsa-source.c
+module_alsa_source_la_LDFLAGS = -module -avoid-version
+module_alsa_source_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la
+module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+# Solaris
+
+module_solaris_la_SOURCES = modules/module-solaris.c
+module_solaris_la_LDFLAGS = -module -avoid-version
+module_solaris_la_LIBADD = $(AM_LIBADD) libiochannel.la
+
+# HOWL
+
+module_zeroconf_publish_la_SOURCES = modules/module-zeroconf-publish.c
+module_zeroconf_publish_la_LDFLAGS = -module -avoid-version
+module_zeroconf_publish_la_LIBADD = $(AM_LIBADD) $(HOWL_LIBS) libhowl-wrap.la
+module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(HOWL_CFLAGS)
+
+# LIRC
+
+module_lirc_la_SOURCES = modules/module-lirc.c
+module_lirc_la_LDFLAGS = -module -avoid-version
+module_lirc_la_LIBADD = $(AM_LIBADD) $(LIRC_LIBS)
+module_lirc_la_CFLAGS = $(AM_CFLAGS) $(LIRC_CFLAGS)
+
+# Linux evdev
+
+module_mmkbd_evdev_la_SOURCES = modules/module-mmkbd-evdev.c
+module_mmkbd_evdev_la_LDFLAGS = -module -avoid-version
+module_mmkbd_evdev_la_LIBADD = $(AM_LIBADD)
+module_mmkbd_evdev_la_CFLAGS = $(AM_CFLAGS)
+
+# Windows waveout
+
+module_waveout_la_SOURCES = modules/module-waveout.c
+module_waveout_la_LDFLAGS = -module -avoid-version
+module_waveout_la_LIBADD = $(AM_LIBADD) libpolypcore.la -lwinmm
+module_waveout_la_CFLAGS = $(AM_CFLAGS)
+
+# Hardware autodetection module
+module_detect_la_SOURCES = modules/module-detect.c
+module_detect_la_LDFLAGS = -module -avoid-version
+module_detect_la_LIBADD = $(AM_LIBADD)
+module_detect_la_CFLAGS = $(AM_CFLAGS)
+
+###################################
+# Some minor stuff #
+###################################
+
+suid: polypaudio
+ chown root $<
+ chmod u+s $<
+
+utils/esdcompat.sh: utils/esdcompat.sh.in Makefile
+ sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \
+ -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \
+ -e 's,@POLYPAUDIO_BINARY\@,$(POLYPAUDIO_BINARY),g' < $< > $@
+
+client.conf: client.conf.in Makefile
+ sed -e 's,@POLYPAUDIO_BINARY\@,$(POLYPAUDIO_BINARY),g' < $< > $@
+
+if OS_IS_WIN32
+default.pa: default.pa.win32
+ cp $< $@
+else
+default.pa: default.pa.in Makefile
+ sed -e 's,@POLYPAUDIO_BINARY\@,$(POLYPAUDIO_BINARY),g' < $< > $@
+endif
+
+daemon.conf: daemon.conf.in Makefile
+ sed -e 's,@DLSEARCHPATH\@,$(modlibdir),g' \
+ -e 's,@DEFAULT_CONFIG_FILE\@,$(DEFAULT_CONFIG_DIR),g' < $< > $@
+
+install-exec-hook:
+ chown root $(DESTDIR)$(bindir)/polypaudio ; true
+ chmod u+s $(DESTDIR)$(bindir)/polypaudio
+ ln -sf pacat $(DESTDIR)$(bindir)/parec
diff --git a/src/client.conf.in b/src/client.conf.in
new file mode 100644
index 00000000..fbf645a4
--- /dev/null
+++ b/src/client.conf.in
@@ -0,0 +1,39 @@
+# $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.
+
+## Configuration file for polypaudio clients. Default values are
+## commented out. Use either ; or # for commenting
+
+## Path to the polypaudio daemon to run when autospawning.
+; daemon-binary = @POLYPAUDIO_BINARY@
+
+## Extra arguments to pass to the polypaudio daemon
+; extra-arguments = --log-target=syslog --exit-idle-time=5
+
+## The default sink to connect to
+; default-sink =
+
+## The default source to connect to
+; default-source =
+
+## The default sever to connect to
+; default-server =
+
+## Autospawn daemons?
+; autospawn = 0
diff --git a/src/daemon.conf.in b/src/daemon.conf.in
new file mode 100644
index 00000000..d5373018
--- /dev/null
+++ b/src/daemon.conf.in
@@ -0,0 +1,77 @@
+# $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.
+
+## Configuration file for the polypaudio daemon. Default values are
+## commented out. Use either ; or # for commenting
+
+# Extra verbositiy
+; verbose = 0
+
+## Daemonize after startup
+; daemonize = 0
+
+## Quit if startup fails
+; fail = 1
+
+## Renice the daemon to level -15 and try to get SCHED_FIFO
+## scheduling. This a good idea if you hear annyoing noise in the
+## playback. However, this is a certain security issue, since it works
+## when called SUID root only. root is dropped immediately after gaining
+## the nice level and SCHED_FIFO scheduling on startup.
+; high-priority = 0
+
+## Disallow module loading after startup
+; disallow-module-loading = 0
+
+## Terminate the daemon after the last client quit and this time
+## passed. Use a negative value to disable this feature.
+; exit-idle-time = -1
+
+## Unload autoloaded modules after being idle for this time
+; module-idle-time = 20
+
+## The path were to look for dynamic shared objects (DSOs aka
+## plugins). You may specify more than one path seperated by
+## colons.
+; dl-search-path = @DLSEARCHPATH@
+
+## The default script file to load. Specify an empty string for not
+## loading a default script file. The
+; default-script-file = @DEFAULT_CONFIG_FILE@
+
+## The default log target. Use either "stderr", "syslog" or
+## "auto". The latter is equivalent to "sylog" in case daemonize is
+## true, otherwise to "stderr".
+; log-target = auto
+
+## The resampling algorithm to use. Use one of src-sinc-best-quality,
+## src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold,
+## src-linear, trivial. See the documentation of libsamplerate for an
+## explanation for the different methods. The method 'trivial' is the
+## only algorithm implemented without usage of floating point
+## numbers. If you're tight on CPU consider using this. On the other
+## hand it has the worst quality of all.
+; resample-method = sinc-fastest
+
+## Create a PID file in /tmp/polypaudio-$USER/pid. Of this is enabled
+## you may use commands like "polypaudio --kill" or "polypaudio
+## --check". If you are planning to start more than one polypaudio
+## process per user, you better disable this option since it
+## effectively disables multiple instances.
+; use-pid-file = 1
diff --git a/src/daemon/caps.c b/src/daemon/caps.c
new file mode 100644
index 00000000..8d429459
--- /dev/null
+++ b/src/daemon/caps.c
@@ -0,0 +1,131 @@
+/* $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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
+
+#include <polypcore/log.h>
+#include "caps.h"
+
+#ifdef HAVE_GETUID
+
+/* Drop root rights when called SUID root */
+void pa_drop_root(void) {
+ uid_t uid = getuid();
+
+ if (uid == 0 || geteuid() != 0)
+ return;
+
+ pa_log_info(__FILE__": dropping root rights.\n");
+
+#if defined(HAVE_SETRESUID)
+ setresuid(uid, uid, uid);
+#elif defined(HAVE_SETREUID)
+ setreuid(uid, uid);
+#else
+ setuid(uid);
+ seteuid(uid);
+#endif
+}
+
+#else
+
+void pa_drop_root(void) {
+}
+
+#endif
+
+#ifdef HAVE_SYS_CAPABILITY_H
+
+/* Limit capabilities set to CAPSYS_NICE */
+int pa_limit_caps(void) {
+ int r = -1;
+ cap_t caps;
+ cap_value_t nice_cap = CAP_SYS_NICE;
+
+ caps = cap_init();
+ assert(caps);
+
+ cap_clear(caps);
+
+ cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET);
+ cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET);
+
+ if (cap_set_proc(caps) < 0)
+ goto fail;
+
+ pa_log_info(__FILE__": dropped capabilities successfully.\n");
+
+ r = 0;
+
+fail:
+ cap_free (caps);
+
+ return r;
+}
+
+/* Drop all capabilities, effectively becoming a normal user */
+int pa_drop_caps(void) {
+ cap_t caps;
+ int r = -1;
+
+ caps = cap_init();
+ assert(caps);
+
+ cap_clear(caps);
+
+ if (cap_set_proc(caps) < 0) {
+ pa_log(__FILE__": failed to drop capabilities: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+ cap_free (caps);
+
+ return r;
+}
+
+#else
+
+/* NOOPs in case capabilities are not available. */
+int pa_limit_caps(void) {
+ return 0;
+}
+
+int pa_drop_caps(void) {
+ pa_drop_root();
+ return 0;
+}
+
+#endif
+
diff --git a/src/daemon/caps.h b/src/daemon/caps.h
new file mode 100644
index 00000000..3bb861d1
--- /dev/null
+++ b/src/daemon/caps.h
@@ -0,0 +1,29 @@
+#ifndef foocapshfoo
+#define foocapshfoo
+
+/* $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.
+***/
+
+void pa_drop_root(void);
+int pa_limit_caps(void);
+int pa_drop_caps(void);
+
+#endif
diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c
new file mode 100644
index 00000000..0b5f9ec7
--- /dev/null
+++ b/src/daemon/cmdline.c
@@ -0,0 +1,300 @@
+/* $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 <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/stat.h>
+
+#include "cmdline.h"
+#include <polypcore/util.h>
+#include <polypcore/strbuf.h>
+#include <polypcore/xmalloc.h>
+
+/* Argument codes for getopt_long() */
+enum {
+ ARG_HELP = 256,
+ ARG_VERSION,
+ ARG_DUMP_CONF,
+ ARG_DUMP_MODULES,
+ ARG_DAEMONIZE,
+ ARG_FAIL,
+ ARG_LOG_LEVEL,
+ ARG_HIGH_PRIORITY,
+ ARG_DISALLOW_MODULE_LOADING,
+ ARG_EXIT_IDLE_TIME,
+ ARG_MODULE_IDLE_TIME,
+ ARG_SCACHE_IDLE_TIME,
+ ARG_LOG_TARGET,
+ ARG_LOAD,
+ ARG_FILE,
+ ARG_DL_SEARCH_PATH,
+ ARG_RESAMPLE_METHOD,
+ ARG_KILL,
+ ARG_USE_PID_FILE,
+ ARG_CHECK
+};
+
+/* Tabel for getopt_long() */
+static struct option long_options[] = {
+ {"help", 0, 0, ARG_HELP},
+ {"version", 0, 0, ARG_VERSION},
+ {"dump-conf", 0, 0, ARG_DUMP_CONF},
+ {"dump-modules", 0, 0, ARG_DUMP_MODULES},
+ {"daemonize", 2, 0, ARG_DAEMONIZE},
+ {"fail", 2, 0, ARG_FAIL},
+ {"verbose", 2, 0, ARG_LOG_LEVEL},
+ {"log-level", 2, 0, ARG_LOG_LEVEL},
+ {"high-priority", 2, 0, ARG_HIGH_PRIORITY},
+ {"disallow-module-loading", 2, 0, ARG_DISALLOW_MODULE_LOADING},
+ {"exit-idle-time", 2, 0, ARG_EXIT_IDLE_TIME},
+ {"module-idle-time", 2, 0, ARG_MODULE_IDLE_TIME},
+ {"scache-idle-time", 2, 0, ARG_SCACHE_IDLE_TIME},
+ {"log-target", 1, 0, ARG_LOG_TARGET},
+ {"load", 1, 0, ARG_LOAD},
+ {"file", 1, 0, ARG_FILE},
+ {"dl-search-path", 1, 0, ARG_DL_SEARCH_PATH},
+ {"resample-method", 1, 0, ARG_RESAMPLE_METHOD},
+ {"kill", 0, 0, ARG_KILL},
+ {"use-pid-file", 2, 0, ARG_USE_PID_FILE},
+ {"check", 0, 0, ARG_CHECK},
+ {NULL, 0, 0, 0}
+};
+
+void pa_cmdline_help(const char *argv0) {
+ const char *e;
+
+ if ((e = strrchr(argv0, '/')))
+ e++;
+ else
+ e = argv0;
+
+ printf("%s [options]\n\n"
+ "COMMANDS:\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " --dump-conf Dump default configuration\n"
+ " --dump-modules Dump list of available modules\n"
+ " -k --kill Kill a running daemon\n"
+ " --check Check for a running daemon\n\n"
+
+ "OPTIONS:\n"
+ " -D, --daemonize[=BOOL] Daemonize after startup\n"
+ " --fail[=BOOL] Quit when startup fails\n"
+ " --high-priority[=BOOL] Try to set high process priority\n"
+ " (only available as root)\n"
+ " --disallow-module-loading[=BOOL] Disallow module loading after startup\n"
+ " --exit-idle-time=SECS Terminate the daemon when idle and this\n"
+ " time passed\n"
+ " --module-idle-time=SECS Unload autoloaded modules when idle and\n"
+ " this time passed\n"
+ " --scache-idle-time=SECS Unload autoloaded samples when idle and\n"
+ " this time passed\n"
+ " --log-level[=LEVEL] Increase or set verbosity level\n"
+ " -v Increase the verbosity level\n"
+ " --log-target={auto,syslog,stderr} Specify the log target\n"
+ " -p, --dl-search-path=PATH Set the search path for dynamic shared\n"
+ " objects (plugins)\n"
+ " --resample-method=[METHOD] Use the specified resampling method\n"
+ " (one of src-sinc-medium-quality,\n"
+ " src-sinc-best-quality,src-sinc-fastest\n"
+ " src-zero-order-hold,src-linear,trivial)\n"
+ " --use-pid-file[=BOOL] Create a PID file\n\n"
+
+ "STARTUP SCRIPT:\n"
+ " -L, --load=\"MODULE ARGUMENTS\" Load the specified plugin module with\n"
+ " the specified argument\n"
+ " -F, --file=FILENAME Run the specified script\n"
+ " -C Open a command line on the running TTY\n"
+ " after startup\n\n"
+
+ " -n Don't load default script file\n", e);
+}
+
+int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) {
+ pa_strbuf *buf = NULL;
+ int c;
+ assert(conf && argc && argv);
+
+ buf = pa_strbuf_new();
+
+ if (conf->script_commands)
+ pa_strbuf_puts(buf, conf->script_commands);
+
+ while ((c = getopt_long(argc, argv, "L:F:ChDnp:kv", long_options, NULL)) != -1) {
+ switch (c) {
+ case ARG_HELP:
+ case 'h':
+ conf->cmd = PA_CMD_HELP;
+ break;
+
+ case ARG_VERSION:
+ conf->cmd = PA_CMD_VERSION;
+ break;
+
+ case ARG_DUMP_CONF:
+ conf->cmd = PA_CMD_DUMP_CONF;
+ break;
+
+ case ARG_DUMP_MODULES:
+ conf->cmd = PA_CMD_DUMP_MODULES;
+ break;
+
+ case 'k':
+ case ARG_KILL:
+ conf->cmd = PA_CMD_KILL;
+ break;
+
+ case ARG_CHECK:
+ conf->cmd = PA_CMD_CHECK;
+ break;
+
+ case ARG_LOAD:
+ case 'L':
+ pa_strbuf_printf(buf, "load-module %s\n", optarg);
+ break;
+
+ case ARG_FILE:
+ case 'F':
+ pa_strbuf_printf(buf, ".include %s\n", optarg);
+ break;
+
+ case 'C':
+ pa_strbuf_puts(buf, "load-module module-cli\n");
+ break;
+
+ case ARG_DAEMONIZE:
+ case 'D':
+ if ((conf->daemonize = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+ pa_log(__FILE__": --daemonize expects boolean argument\n");
+ goto fail;
+ }
+ break;
+
+ case ARG_FAIL:
+ if ((conf->fail = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+ pa_log(__FILE__": --fail expects boolean argument\n");
+ goto fail;
+ }
+ break;
+
+ case 'v':
+ case ARG_LOG_LEVEL:
+
+ if (optarg) {
+ if (pa_daemon_conf_set_log_level(conf, optarg) < 0) {
+ pa_log(__FILE__": --log-level expects log level argument (either numeric in range 0..4 or one of debug, info, notice, warn, error).\n");
+ goto fail;
+ }
+ } else {
+ if (conf->log_level < PA_LOG_LEVEL_MAX-1)
+ conf->log_level++;
+ }
+
+ break;
+
+ case ARG_HIGH_PRIORITY:
+ if ((conf->high_priority = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+ pa_log(__FILE__": --high-priority expects boolean argument\n");
+ goto fail;
+ }
+ break;
+
+ case ARG_DISALLOW_MODULE_LOADING:
+ if ((conf->disallow_module_loading = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+ pa_log(__FILE__": --disallow-module-loading expects boolean argument\n");
+ goto fail;
+ }
+ break;
+
+ case ARG_USE_PID_FILE:
+ if ((conf->use_pid_file = optarg ? pa_parse_boolean(optarg) : 1) < 0) {
+ pa_log(__FILE__": --use-pid-file expects boolean argument\n");
+ goto fail;
+ }
+ break;
+
+ case 'p':
+ case ARG_DL_SEARCH_PATH:
+ pa_xfree(conf->dl_search_path);
+ conf->dl_search_path = *optarg ? pa_xstrdup(optarg) : NULL;
+ break;
+
+ case 'n':
+ pa_xfree(conf->default_script_file);
+ conf->default_script_file = NULL;
+ break;
+
+ case ARG_LOG_TARGET:
+ if (pa_daemon_conf_set_log_target(conf, optarg) < 0) {
+ pa_log(__FILE__": Invalid log target: use either 'syslog', 'stderr' or 'auto'.\n");
+ goto fail;
+ }
+ break;
+
+ case ARG_EXIT_IDLE_TIME:
+ conf->exit_idle_time = atoi(optarg);
+ break;
+
+ case ARG_MODULE_IDLE_TIME:
+ conf->module_idle_time = atoi(optarg);
+ break;
+
+ case ARG_SCACHE_IDLE_TIME:
+ conf->scache_idle_time = atoi(optarg);
+ break;
+
+ case ARG_RESAMPLE_METHOD:
+ if (pa_daemon_conf_set_resample_method(conf, optarg) < 0) {
+ pa_log(__FILE__": Invalid resample method '%s'.\n", optarg);
+ goto fail;
+ }
+ break;
+
+ default:
+ goto fail;
+ }
+ }
+
+ pa_xfree(conf->script_commands);
+ conf->script_commands = pa_strbuf_tostring_free(buf);
+
+ if (!conf->script_commands) {
+ pa_xfree(conf->script_commands);
+ conf->script_commands = NULL;
+ }
+
+ *d = optind;
+
+ return 0;
+
+fail:
+ if (buf)
+ pa_strbuf_free(buf);
+
+ return -1;
+}
diff --git a/src/daemon/cmdline.h b/src/daemon/cmdline.h
new file mode 100644
index 00000000..e2eaf0d2
--- /dev/null
+++ b/src/daemon/cmdline.h
@@ -0,0 +1,35 @@
+#ifndef foocmdlinehfoo
+#define foocmdlinehfoo
+
+/* $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 "daemon-conf.h"
+
+/* Parese the command line and store its data in *c. Return the index
+ * of the first unparsed argument in *d. */
+int pa_cmdline_parse(pa_daemon_conf*c, int argc, char *const argv [], int *d);
+
+/* Show the command line help. The command name is extracted from
+ * argv[0] which should be passed in argv0. */
+void pa_cmdline_help(const char *argv0);
+
+#endif
diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c
new file mode 100644
index 00000000..6887796f
--- /dev/null
+++ b/src/daemon/cpulimit.c
@@ -0,0 +1,236 @@
+/* $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 "cpulimit.h"
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+
+#ifdef HAVE_SIGXCPU
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+/* This module implements a watchdog that makes sure that the current
+ * process doesn't consume more than 70% CPU time for 10 seconds. This
+ * is very useful when using SCHED_FIFO scheduling which effectively
+ * disables multitasking. */
+
+/* Method of operation: Using SIGXCPU a signal handler is called every
+ * 10s process CPU time. That function checks if less than 14s system
+ * time have passed. In that case, it tries to contact the main event
+ * loop through a pipe. After two additional seconds it is checked
+ * whether the main event loop contact was successful. If not, the
+ * program is terminated forcibly. */
+
+/* Utilize this much CPU time at maximum */
+#define CPUTIME_PERCENT 70
+
+/* Check every 10s */
+#define CPUTIME_INTERVAL_SOFT (10)
+
+/* Recheck after 2s */
+#define CPUTIME_INTERVAL_HARD (2)
+
+/* Time of the last CPU load check */
+static time_t last_time = 0;
+
+/* Pipe for communicating with the main loop */
+static int the_pipe[2] = {-1, -1};
+
+/* Main event loop and IO event for the FIFO */
+static pa_mainloop_api *api = NULL;
+static pa_io_event *io_event = NULL;
+
+/* Saved sigaction struct for SIGXCPU */
+static struct sigaction sigaction_prev;
+
+/* Nonzero after pa_cpu_limit_init() */
+static int installed = 0;
+
+/* The current state of operation */
+static enum {
+ PHASE_IDLE, /* Normal state */
+ PHASE_SOFT /* After CPU overload has been detected */
+} phase = PHASE_IDLE;
+
+/* Reset the SIGXCPU timer to the next t seconds */
+static void reset_cpu_time(int t) {
+ int r;
+ long n;
+ struct rlimit rl;
+ struct rusage ru;
+
+ /* Get the current CPU time of the current process */
+ r = getrusage(RUSAGE_SELF, &ru);
+ assert(r >= 0);
+
+ n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t;
+
+ r = getrlimit(RLIMIT_CPU, &rl);
+ assert(r >= 0);
+
+ rl.rlim_cur = n;
+ r = setrlimit(RLIMIT_CPU, &rl);
+ assert(r >= 0);
+}
+
+/* A simple, thread-safe puts() work-alike */
+static void write_err(const char *p) {
+ pa_loop_write(2, p, strlen(p));
+}
+
+/* The signal handler, called on every SIGXCPU */
+static void signal_handler(int sig) {
+ assert(sig == SIGXCPU);
+
+ if (phase == PHASE_IDLE) {
+ time_t now;
+
+#ifdef PRINT_CPU_LOAD
+ char t[256];
+#endif
+
+ time(&now);
+
+#ifdef PRINT_CPU_LOAD
+ snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100);
+ write_err(t);
+#endif
+
+ if (CPUTIME_INTERVAL_SOFT >= ((now-last_time)*(double)CPUTIME_PERCENT/100)) {
+ static const char c = 'X';
+
+ write_err("Soft CPU time limit exhausted, terminating.\n");
+
+ /* Try a soft cleanup */
+ write(the_pipe[1], &c, sizeof(c));
+ phase = PHASE_SOFT;
+ reset_cpu_time(CPUTIME_INTERVAL_HARD);
+
+ } else {
+
+ /* Everything's fine */
+ reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+ last_time = now;
+ }
+
+ } else if (phase == PHASE_SOFT) {
+ write_err("Hard CPU time limit exhausted, terminating forcibly.\n");
+ _exit(1); /* Forced exit */
+ }
+}
+
+/* Callback for IO events on the FIFO */
+static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags f, void *userdata) {
+ char c;
+ assert(m && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == the_pipe[0]);
+ read(the_pipe[0], &c, sizeof(c));
+ m->quit(m, 1); /* Quit the main loop */
+}
+
+/* Initializes CPU load limiter */
+int pa_cpu_limit_init(pa_mainloop_api *m) {
+ struct sigaction sa;
+ assert(m && !api && !io_event && the_pipe[0] == -1 && the_pipe[1] == -1 && !installed);
+
+ time(&last_time);
+
+ /* Prepare the main loop pipe */
+ if (pipe(the_pipe) < 0) {
+ pa_log(__FILE__": pipe() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ pa_make_nonblock_fd(the_pipe[0]);
+ pa_make_nonblock_fd(the_pipe[1]);
+ pa_fd_set_cloexec(the_pipe[0], 1);
+ pa_fd_set_cloexec(the_pipe[1], 1);
+
+ api = m;
+ io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL);
+
+ phase = PHASE_IDLE;
+
+ /* Install signal handler for SIGXCPU */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(SIGXCPU, &sa, &sigaction_prev) < 0) {
+ pa_cpu_limit_done();
+ return -1;
+ }
+
+ installed = 1;
+
+ reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+
+ return 0;
+}
+
+/* Shutdown CPU load limiter */
+void pa_cpu_limit_done(void) {
+ int r;
+
+ if (io_event) {
+ assert(api);
+ api->io_free(io_event);
+ io_event = NULL;
+ api = NULL;
+ }
+
+ if (the_pipe[0] >= 0)
+ close(the_pipe[0]);
+ if (the_pipe[1] >= 0)
+ close(the_pipe[1]);
+ the_pipe[0] = the_pipe[1] = -1;
+
+ if (installed) {
+ r = sigaction(SIGXCPU, &sigaction_prev, NULL);
+ assert(r >= 0);
+ installed = 0;
+ }
+}
+
+#else /* HAVE_SIGXCPU */
+
+int pa_cpu_limit_init(PA_GCC_UNUSED pa_mainloop_api *m) {
+ return 0;
+}
+
+void pa_cpu_limit_done(void) {
+}
+
+#endif
diff --git a/src/daemon/cpulimit.h b/src/daemon/cpulimit.h
new file mode 100644
index 00000000..f3c5534d
--- /dev/null
+++ b/src/daemon/cpulimit.h
@@ -0,0 +1,34 @@
+#ifndef foocpulimithfoo
+#define foocpulimithfoo
+
+/* $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>
+
+/* This kills the polypaudio process if it eats more than 70% of the
+ * CPU time. This is build around setrlimit() and SIGXCPU. It is handy
+ * in case of using SCHED_FIFO which may freeze the whole machine */
+
+int pa_cpu_limit_init(pa_mainloop_api *m);
+void pa_cpu_limit_done(void);
+
+#endif
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
new file mode 100644
index 00000000..8fe3c4cc
--- /dev/null
+++ b/src/daemon/daemon-conf.c
@@ -0,0 +1,297 @@
+/* $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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include "daemon-conf.h"
+#include <polypcore/util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/strbuf.h>
+#include <polypcore/conf-parser.h>
+#include <polypcore/resampler.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_SCRIPT_FILE DEFAULT_CONFIG_DIR PATH_SEP "default.pa"
+#define DEFAULT_SCRIPT_FILE_USER ".polypaudio" PATH_SEP "default.pa"
+#define DEFAULT_CONFIG_FILE DEFAULT_CONFIG_DIR PATH_SEP "daemon.conf"
+#define DEFAULT_CONFIG_FILE_USER ".polypaudio" PATH_SEP "daemon.conf"
+
+#define ENV_SCRIPT_FILE "POLYP_SCRIPT"
+#define ENV_CONFIG_FILE "POLYP_CONFIG"
+#define ENV_DL_SEARCH_PATH "POLYP_DLPATH"
+
+static const pa_daemon_conf default_conf = {
+ .cmd = PA_CMD_DAEMON,
+ .daemonize = 0,
+ .fail = 1,
+ .high_priority = 0,
+ .disallow_module_loading = 0,
+ .exit_idle_time = -1,
+ .module_idle_time = 20,
+ .scache_idle_time = 20,
+ .auto_log_target = 1,
+ .script_commands = NULL,
+ .dl_search_path = NULL,
+ .default_script_file = NULL,
+ .log_target = PA_LOG_SYSLOG,
+ .log_level = PA_LOG_NOTICE,
+ .resample_method = PA_RESAMPLER_SRC_SINC_FASTEST,
+ .config_file = NULL,
+ .use_pid_file = 1
+};
+
+pa_daemon_conf* pa_daemon_conf_new(void) {
+ FILE *f;
+ pa_daemon_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf));
+
+ if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file)))
+ fclose(f);
+
+#ifdef DLSEARCHPATH
+ c->dl_search_path = pa_xstrdup(DLSEARCHPATH);
+#endif
+ return c;
+}
+
+void pa_daemon_conf_free(pa_daemon_conf *c) {
+ assert(c);
+ pa_xfree(c->script_commands);
+ pa_xfree(c->dl_search_path);
+ pa_xfree(c->default_script_file);
+ pa_xfree(c->config_file);
+ pa_xfree(c);
+}
+
+int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) {
+ assert(c && string);
+
+ if (!strcmp(string, "auto"))
+ c->auto_log_target = 1;
+ else if (!strcmp(string, "syslog")) {
+ c->auto_log_target = 0;
+ c->log_target = PA_LOG_SYSLOG;
+ } else if (!strcmp(string, "stderr")) {
+ c->auto_log_target = 0;
+ c->log_target = PA_LOG_STDERR;
+ } else
+ return -1;
+
+ return 0;
+}
+
+int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string) {
+ uint32_t u;
+ assert(c && string);
+
+ if (pa_atou(string, &u) >= 0) {
+ if (u >= PA_LOG_LEVEL_MAX)
+ return -1;
+
+ c->log_level = (pa_log_level_t) u;
+ } else if (pa_startswith(string, "debug"))
+ c->log_level = PA_LOG_DEBUG;
+ else if (pa_startswith(string, "info"))
+ c->log_level = PA_LOG_INFO;
+ else if (pa_startswith(string, "notice"))
+ c->log_level = PA_LOG_NOTICE;
+ else if (pa_startswith(string, "warn"))
+ c->log_level = PA_LOG_WARN;
+ else if (pa_startswith(string, "err"))
+ c->log_level = PA_LOG_ERROR;
+ else
+ return -1;
+
+ return 0;
+}
+
+int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) {
+ int m;
+ assert(c && string);
+
+ if ((m = pa_parse_resample_method(string)) < 0)
+ return -1;
+
+ c->resample_method = m;
+ return 0;
+}
+
+static int parse_log_target(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ assert(filename && lvalue && rvalue && data);
+
+ if (pa_daemon_conf_set_log_target(c, rvalue) < 0) {
+ pa_log(__FILE__": [%s:%u] Invalid log target '%s'.\n", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_log_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ assert(filename && lvalue && rvalue && data);
+
+ if (pa_daemon_conf_set_log_level(c, rvalue) < 0) {
+ pa_log(__FILE__": [%s:%u] Invalid log level '%s'.\n", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_resample_method(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ assert(filename && lvalue && rvalue && data);
+
+ if (pa_daemon_conf_set_resample_method(c, rvalue) < 0) {
+ pa_log(__FILE__": [%s:%u] Inavalid resample method '%s'.\n", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
+ int r = -1;
+ FILE *f = NULL;
+
+ pa_config_item table[] = {
+ { "daemonize", pa_config_parse_bool, NULL },
+ { "fail", pa_config_parse_bool, NULL },
+ { "high-priority", pa_config_parse_bool, NULL },
+ { "disallow-module-loading", pa_config_parse_bool, NULL },
+ { "exit-idle-time", pa_config_parse_int, NULL },
+ { "module-idle-time", pa_config_parse_int, NULL },
+ { "scache-idle-time", pa_config_parse_int, NULL },
+ { "dl-search-path", pa_config_parse_string, NULL },
+ { "default-script-file", pa_config_parse_string, NULL },
+ { "log-target", parse_log_target, NULL },
+ { "log-level", parse_log_level, NULL },
+ { "verbose", parse_log_level, NULL },
+ { "resample-method", parse_resample_method, NULL },
+ { "use-pid-file", pa_config_parse_bool, NULL },
+ { NULL, NULL, NULL },
+ };
+
+ table[0].data = &c->daemonize;
+ table[1].data = &c->fail;
+ table[2].data = &c->high_priority;
+ table[3].data = &c->disallow_module_loading;
+ table[4].data = &c->exit_idle_time;
+ table[5].data = &c->module_idle_time;
+ table[6].data = &c->scache_idle_time;
+ table[7].data = &c->dl_search_path;
+ table[8].data = &c->default_script_file;
+ table[9].data = c;
+ table[10].data = c;
+ table[11].data = c;
+ table[12].data = c;
+ table[13].data = &c->use_pid_file;
+
+ pa_xfree(c->config_file);
+ c->config_file = NULL;
+
+ f = filename ?
+ fopen(c->config_file = pa_xstrdup(filename), "r") :
+ pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
+
+ if (!f && errno != ENOENT) {
+ pa_log(__FILE__": WARNING: failed to open configuration file '%s': %s\n", filename, strerror(errno));
+ goto finish;
+ }
+
+ r = f ? pa_config_parse(c->config_file, f, table, NULL) : 0;
+
+finish:
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int pa_daemon_conf_env(pa_daemon_conf *c) {
+ char *e;
+
+ if ((e = getenv(ENV_DL_SEARCH_PATH))) {
+ pa_xfree(c->dl_search_path);
+ c->dl_search_path = pa_xstrdup(e);
+ }
+ if ((e = getenv(ENV_SCRIPT_FILE))) {
+ pa_xfree(c->default_script_file);
+ c->default_script_file = pa_xstrdup(e);
+ }
+
+ return 0;
+}
+
+static const char* const log_level_to_string[] = {
+ [PA_LOG_DEBUG] = "debug",
+ [PA_LOG_INFO] = "info",
+ [PA_LOG_NOTICE] = "notice",
+ [PA_LOG_WARN] = "warning",
+ [PA_LOG_ERROR] = "error"
+};
+
+char *pa_daemon_conf_dump(pa_daemon_conf *c) {
+ pa_strbuf *s = pa_strbuf_new();
+
+ if (c->config_file)
+ pa_strbuf_printf(s, "### Read from configuration file: %s ###\n", c->config_file);
+
+ assert(c->log_level <= PA_LOG_LEVEL_MAX);
+
+ pa_strbuf_printf(s, "daemonize = %i\n", !!c->daemonize);
+ pa_strbuf_printf(s, "fail = %i\n", !!c->fail);
+ pa_strbuf_printf(s, "high-priority = %i\n", !!c->high_priority);
+ pa_strbuf_printf(s, "disallow-module-loading = %i\n", !!c->disallow_module_loading);
+ pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time);
+ pa_strbuf_printf(s, "module-idle-time = %i\n", c->module_idle_time);
+ pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);
+ pa_strbuf_printf(s, "dl-search-path = %s\n", c->dl_search_path ? c->dl_search_path : "");
+ pa_strbuf_printf(s, "default-script-file = %s\n", c->default_script_file);
+ pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr"));
+ pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]);
+ pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
+ pa_strbuf_printf(s, "use-pid-file = %i\n", c->use_pid_file);
+
+ return pa_strbuf_tostring_free(s);
+}
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
new file mode 100644
index 00000000..d5131419
--- /dev/null
+++ b/src/daemon/daemon-conf.h
@@ -0,0 +1,80 @@
+#ifndef foodaemonconfhfoo
+#define foodaemonconfhfoo
+
+/* $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/log.h>
+
+/* The actual command to execute */
+typedef enum pa_daemon_conf_cmd {
+ PA_CMD_DAEMON, /* the default */
+ PA_CMD_HELP,
+ PA_CMD_VERSION,
+ PA_CMD_DUMP_CONF,
+ PA_CMD_DUMP_MODULES,
+ PA_CMD_KILL,
+ PA_CMD_CHECK
+} pa_daemon_conf_cmd_t;
+
+/* A structure containing configuration data for the Polypaudio server . */
+typedef struct pa_daemon_conf {
+ pa_daemon_conf_cmd_t cmd;
+ int daemonize,
+ fail,
+ high_priority,
+ disallow_module_loading,
+ exit_idle_time,
+ module_idle_time,
+ scache_idle_time,
+ auto_log_target,
+ use_pid_file;
+ char *script_commands, *dl_search_path, *default_script_file;
+ pa_log_target_t log_target;
+ pa_log_level_t log_level;
+ int resample_method;
+ char *config_file;
+} pa_daemon_conf;
+
+/* Allocate a new structure and fill it with sane defaults */
+pa_daemon_conf* pa_daemon_conf_new(void);
+void pa_daemon_conf_free(pa_daemon_conf*c);
+
+/* Load configuration data from the specified file overwriting the
+ * current settings in *c. If filename is NULL load the default daemon
+ * configuration file */
+int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename);
+
+/* Pretty print the current configuration data of the daemon. The
+ * returned string has to be freed manually. The output of this
+ * function may be parsed with pa_daemon_conf_load(). */
+char *pa_daemon_conf_dump(pa_daemon_conf *c);
+
+/* Load the configuration data from the process' environment
+ * overwriting the current settings in *c. */
+int pa_daemon_conf_env(pa_daemon_conf *c);
+
+/* Set these configuration variables in the structure by passing a string */
+int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string);
+int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string);
+int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string);
+
+#endif
diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c
new file mode 100644
index 00000000..8d8eb0b9
--- /dev/null
+++ b/src/daemon/dumpmodules.c
@@ -0,0 +1,99 @@
+/* $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 <string.h>
+#include <getopt.h>
+#include <assert.h>
+#include <stdio.h>
+#include <ltdl.h>
+
+#include "dumpmodules.h"
+#include <polypcore/modinfo.h>
+#include <polypcore/util.h>
+
+#define PREFIX "module-"
+
+static void short_info(const char *name, PA_GCC_UNUSED const char *path, pa_modinfo *i) {
+ assert(name && i);
+ printf("%-40s%s\n", name, i->description ? i->description : "n/a");
+}
+
+static void long_info(const char *name, const char *path, pa_modinfo *i) {
+ static int nl = 0;
+ assert(name && i);
+
+ if (nl)
+ printf("\n");
+
+ nl = 1;
+
+ printf("Name: %s\n", name);
+
+ if (!i->description && !i->version && !i->author && !i->usage)
+ printf("No module information available\n");
+ else {
+ if (i->version)
+ printf("Version: %s\n", i->version);
+ if (i->description)
+ printf("Description: %s\n", i->description);
+ if (i->author)
+ printf("Author: %s\n", i->author);
+ if (i->usage)
+ printf("Usage: %s\n", i->usage);
+ }
+
+ if (path)
+ printf("Path: %s\n", path);
+}
+
+static void show_info(const char *name, const char *path, void (*info)(const char *name, const char *path, pa_modinfo*i)) {
+ pa_modinfo *i;
+
+ if ((i = pa_modinfo_get_by_name(path ? path : name))) {
+ info(name, path, i);
+ pa_modinfo_free(i);
+ }
+}
+
+static int callback(const char *path, lt_ptr data) {
+ const char *e;
+ pa_daemon_conf *c = (data);
+
+ e = pa_path_get_filename(path);
+
+ if (strlen(e) > sizeof(PREFIX)-1 && !strncmp(e, PREFIX, sizeof(PREFIX)-1))
+ show_info(e, path, c->log_level >= PA_LOG_INFO ? long_info : short_info);
+
+ return 0;
+}
+
+void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]) {
+ if (argc > 0) {
+ int i;
+ for (i = 0; i < argc; i++)
+ show_info(argv[i], NULL, long_info);
+ } else
+ lt_dlforeachfile(NULL, callback, c);
+}
diff --git a/src/daemon/dumpmodules.h b/src/daemon/dumpmodules.h
new file mode 100644
index 00000000..968d2de9
--- /dev/null
+++ b/src/daemon/dumpmodules.h
@@ -0,0 +1,31 @@
+#ifndef foodumpmoduleshfoo
+#define foodumpmoduleshfoo
+
+/* $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 "daemon-conf.h"
+
+/* Dump all available modules to STDOUT. If argc > 0 print information
+ * about the modules specified in argv[] instead. */
+void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]);
+
+#endif
diff --git a/src/daemon/main.c b/src/daemon/main.c
new file mode 100644
index 00000000..6be83d8c
--- /dev/null
+++ b/src/daemon/main.c
@@ -0,0 +1,472 @@
+/* $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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stddef.h>
+#include <assert.h>
+#include <ltdl.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <liboil/liboil.h>
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <syslog.h>
+#include <tcpd.h>
+#endif
+
+#include <polypcore/winsock.h>
+
+#include <polypcore/core.h>
+#include <polypcore/memblock.h>
+#include <polyp/mainloop.h>
+#include <polypcore/module.h>
+#include <polyp/mainloop-signal.h>
+#include "cmdline.h"
+#include <polypcore/cli-command.h>
+#include <polypcore/util.h>
+#include <polypcore/sioman.h>
+#include <polypcore/xmalloc.h>
+#include "cpulimit.h"
+#include <polypcore/log.h>
+#include "daemon-conf.h"
+#include "dumpmodules.h"
+#include "caps.h"
+#include <polypcore/cli-text.h>
+#include <polypcore/pid.h>
+#include <polypcore/namereg.h>
+
+#ifdef HAVE_LIBWRAP
+/* Only one instance of these variables */
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;
+#endif
+
+#ifdef OS_IS_WIN32
+
+static void message_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ MSG msg;
+
+ while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message == WM_QUIT)
+ raise(SIGTERM);
+ else {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+}
+
+#endif
+
+static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, int sig, void *userdata) {
+ pa_log_info(__FILE__": Got signal %s.\n", pa_strsignal(sig));
+
+ switch (sig) {
+#ifdef SIGUSR1
+ case SIGUSR1:
+ pa_module_load(userdata, "module-cli", NULL);
+ break;
+#endif
+
+#ifdef SIGUSR2
+ case SIGUSR2:
+ pa_module_load(userdata, "module-cli-protocol-unix", NULL);
+ break;
+#endif
+
+#ifdef SIGHUP
+ case SIGHUP: {
+ char *c = pa_full_status_string(userdata);
+ pa_log_notice(c);
+ pa_xfree(c);
+ return;
+ }
+#endif
+
+ case SIGINT:
+ case SIGTERM:
+ default:
+ pa_log_info(__FILE__": Exiting.\n");
+ m->quit(m, 1);
+ break;
+ }
+}
+
+static void close_pipe(int p[2]) {
+ if (p[0] != -1)
+ close(p[0]);
+ if (p[1] != -1)
+ close(p[1]);
+ p[0] = p[1] = -1;
+}
+
+int main(int argc, char *argv[]) {
+ pa_core *c;
+ pa_strbuf *buf = NULL;
+ pa_daemon_conf *conf;
+ pa_mainloop *mainloop;
+
+ char *s;
+ int r, retval = 1, d = 0;
+ int daemon_pipe[2] = { -1, -1 };
+ int suid_root;
+ int valid_pid_file = 0;
+
+#ifdef HAVE_GETUID
+ gid_t gid = (gid_t) -1;
+#endif
+
+#ifdef OS_IS_WIN32
+ pa_defer_event *defer;
+#endif
+
+ pa_limit_caps();
+
+#ifdef HAVE_GETUID
+ suid_root = getuid() != 0 && geteuid() == 0;
+
+ if (suid_root && (pa_uid_in_group("realtime", &gid) <= 0 || gid >= 1000)) {
+ pa_log_warn(__FILE__": WARNING: called SUID root, but not in group 'realtime'.\n");
+ pa_drop_root();
+ }
+#else
+ suid_root = 0;
+#endif
+
+ LTDL_SET_PRELOADED_SYMBOLS();
+
+ r = lt_dlinit();
+ assert(r == 0);
+
+#ifdef OS_IS_WIN32
+ {
+ WSADATA data;
+ WSAStartup(MAKEWORD(2, 0), &data);
+ }
+#endif
+
+ pa_log_set_ident("polypaudio");
+
+ conf = pa_daemon_conf_new();
+
+ if (pa_daemon_conf_load(conf, NULL) < 0)
+ goto finish;
+
+ if (pa_daemon_conf_env(conf) < 0)
+ goto finish;
+
+ if (pa_cmdline_parse(conf, argc, argv, &d) < 0) {
+ pa_log(__FILE__": failed to parse command line.\n");
+ goto finish;
+ }
+
+ pa_log_set_maximal_level(conf->log_level);
+ pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL);
+
+ if (conf->high_priority && conf->cmd == PA_CMD_DAEMON)
+ pa_raise_priority();
+
+ pa_drop_caps();
+
+ if (suid_root)
+ pa_drop_root();
+
+ if (conf->dl_search_path)
+ lt_dlsetsearchpath(conf->dl_search_path);
+
+ switch (conf->cmd) {
+ case PA_CMD_DUMP_MODULES:
+ pa_dump_modules(conf, argc-d, argv+d);
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_DUMP_CONF: {
+ s = pa_daemon_conf_dump(conf);
+ fputs(s, stdout);
+ pa_xfree(s);
+ retval = 0;
+ goto finish;
+ }
+
+ case PA_CMD_HELP :
+ pa_cmdline_help(argv[0]);
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_VERSION :
+ printf(PACKAGE_NAME" "PACKAGE_VERSION"\n");
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_CHECK: {
+ pid_t pid;
+
+ if (pa_pid_file_check_running(&pid) < 0) {
+ pa_log_info(__FILE__": daemon not running\n");
+ } else {
+ pa_log_info(__FILE__": daemon running as PID %u\n", pid);
+ retval = 0;
+ }
+
+ goto finish;
+
+ }
+ case PA_CMD_KILL:
+
+ if (pa_pid_file_kill(SIGINT, NULL) < 0)
+ pa_log(__FILE__": failed to kill daemon.\n");
+ else
+ retval = 0;
+
+ goto finish;
+
+ default:
+ assert(conf->cmd == PA_CMD_DAEMON);
+ }
+
+ if (conf->daemonize) {
+ pid_t child;
+ int tty_fd;
+
+ if (pa_stdio_acquire() < 0) {
+ pa_log(__FILE__": failed to acquire stdio.\n");
+ goto finish;
+ }
+
+#ifdef HAVE_FORK
+ if (pipe(daemon_pipe) < 0) {
+ pa_log(__FILE__": failed to create pipe.\n");
+ goto finish;
+ }
+
+ if ((child = fork()) < 0) {
+ pa_log(__FILE__": fork() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ if (child != 0) {
+ /* Father */
+
+ close(daemon_pipe[1]);
+ daemon_pipe[1] = -1;
+
+ if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval)) != sizeof(retval)) {
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ retval = 1;
+ }
+
+ if (retval)
+ pa_log(__FILE__": daemon startup failed.\n");
+ else
+ pa_log_info(__FILE__": daemon startup successful.\n");
+
+ goto finish;
+ }
+
+ close(daemon_pipe[0]);
+ daemon_pipe[0] = -1;
+#endif
+
+ if (conf->auto_log_target)
+ pa_log_set_target(PA_LOG_SYSLOG, NULL);
+
+#ifdef HAVE_SETSID
+ setsid();
+#endif
+#ifdef HAVE_SETPGID
+ setpgid(0,0);
+#endif
+
+#ifndef OS_IS_WIN32
+ close(0);
+ close(1);
+ close(2);
+
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_WRONLY);
+ open("/dev/null", O_WRONLY);
+#else
+ FreeConsole();
+#endif
+
+#ifdef SIGTTOU
+ signal(SIGTTOU, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+ signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTSTP
+ signal(SIGTSTP, SIG_IGN);
+#endif
+
+#ifdef TIOCNOTTY
+ if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) {
+ ioctl(tty_fd, TIOCNOTTY, (char*) 0);
+ close(tty_fd);
+ }
+#endif
+ }
+
+ chdir("/");
+
+ if (conf->use_pid_file) {
+ if (pa_pid_file_create() < 0) {
+ pa_log(__FILE__": pa_pid_file_create() failed.\n");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval));
+#endif
+ goto finish;
+ }
+
+ valid_pid_file = 1;
+ }
+
+ mainloop = pa_mainloop_new();
+ assert(mainloop);
+
+ c = pa_core_new(pa_mainloop_get_api(mainloop));
+ assert(c);
+
+ r = pa_signal_init(pa_mainloop_get_api(mainloop));
+ assert(r == 0);
+ pa_signal_new(SIGINT, signal_callback, c);
+ pa_signal_new(SIGTERM, signal_callback, c);
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef OS_IS_WIN32
+ defer = pa_mainloop_get_api(mainloop)->defer_new(pa_mainloop_get_api(mainloop), message_cb, NULL);
+ assert(defer);
+#endif
+
+ if (conf->daemonize)
+ c->running_as_daemon = 1;
+
+#ifdef SIGUSR1
+ pa_signal_new(SIGUSR1, signal_callback, c);
+#endif
+#ifdef SIGUSR2
+ pa_signal_new(SIGUSR2, signal_callback, c);
+#endif
+#ifdef SIGHUP
+ pa_signal_new(SIGHUP, signal_callback, c);
+#endif
+
+ oil_init();
+
+ r = pa_cpu_limit_init(pa_mainloop_get_api(mainloop));
+ assert(r == 0);
+
+ buf = pa_strbuf_new();
+ assert(buf);
+ if (conf->default_script_file)
+ r = pa_cli_command_execute_file(c, conf->default_script_file, buf, &conf->fail);
+
+ if (r >= 0)
+ r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail);
+ pa_log(s = pa_strbuf_tostring_free(buf));
+ pa_xfree(s);
+
+ if (r < 0 && conf->fail) {
+ pa_log(__FILE__": failed to initialize daemon.\n");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval));
+#endif
+ } else if (!c->modules || pa_idxset_size(c->modules) == 0) {
+ pa_log(__FILE__": daemon startup without any loaded modules, refusing to work.\n");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval));
+#endif
+ } else {
+
+ retval = 0;
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval));
+#endif
+
+ c->disallow_module_loading = conf->disallow_module_loading;
+ c->exit_idle_time = conf->exit_idle_time;
+ c->module_idle_time = conf->module_idle_time;
+ c->scache_idle_time = conf->scache_idle_time;
+ c->resample_method = conf->resample_method;
+
+ if (c->default_sink_name &&
+ pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, 1) == NULL) {
+ pa_log_error("%s : Fatal error. Default sink name (%s) does not exist in name register.\n", __FILE__, c->default_sink_name);
+ retval = 1;
+ } else {
+ pa_log_info(__FILE__": Daemon startup complete.\n");
+ if (pa_mainloop_run(mainloop, &retval) < 0)
+ retval = 1;
+ pa_log_info(__FILE__": Daemon shutdown initiated.\n");
+ }
+ }
+
+#ifdef OS_IS_WIN32
+ pa_mainloop_get_api(mainloop)->defer_free(defer);
+#endif
+
+ pa_core_free(c);
+
+ pa_cpu_limit_done();
+ pa_signal_done();
+ pa_mainloop_free(mainloop);
+
+ pa_log_info(__FILE__": Daemon terminated.\n");
+
+finish:
+
+ if (conf)
+ pa_daemon_conf_free(conf);
+
+ if (valid_pid_file)
+ pa_pid_file_remove();
+
+ close_pipe(daemon_pipe);
+
+#ifdef OS_IS_WIN32
+ WSACleanup();
+#endif
+
+ lt_dlexit();
+
+ return retval;
+}
diff --git a/src/default.pa.in b/src/default.pa.in
new file mode 100755
index 00000000..3aaeeaf0
--- /dev/null
+++ b/src/default.pa.in
@@ -0,0 +1,66 @@
+#!@POLYPAUDIO_BINARY@ -nF
+
+#
+# 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.
+
+
+# Load audio drivers statically
+
+#load-module module-alsa-sink
+# load-module module-alsa-source device=plughw:1,0
+load-module module-oss device="/dev/dsp" sink_name=output source_name=input
+#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
+load-module module-null-sink
+#load-module module-pipe-sink
+
+# Load audio drivers automatically on access
+
+#add-autoload-sink output module-oss device="/dev/dsp" sink_name=output source_name=input
+#add-autoload-source input module-oss device="/dev/dsp" sink_name=output source_name=input
+#add-autoload-sink output module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
+#add-autoload-source input module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
+#add-autoload-sink output module-alsa-sink sink_name=output
+#add-autoload-source input module-alsa-source source_name=input
+
+# Load several protocols
+load-module module-esound-protocol-unix
+#load-module module-esound-protocol-tcp
+load-module module-native-protocol-unix
+#load-module module-simple-protocol-tcp
+#load-module module-cli-protocol-unix
+
+# Load the CLI module
+load-module module-cli
+
+# Make some devices default
+set-default-sink output
+set-default-source input
+
+.nofail
+
+# Load something to the sample cache
+load-sample x11-bell /usr/share/sounds/KDE_Notify.wav
+load-sample-dir-lazy /usr/share/sounds/*.wav
+
+# Load X11 bell module
+load-module module-x11-bell sample=x11-bell sink=output
+
+# Publish connection data in the X11 root window
+load-module module-x11-publish
+
+#load-module module-pipe-source
+#load-module module-pipe-sink
diff --git a/src/default.pa.win32 b/src/default.pa.win32
new file mode 100644
index 00000000..3478adab
--- /dev/null
+++ b/src/default.pa.win32
@@ -0,0 +1,43 @@
+#
+# 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.
+
+
+# Load audio drivers statically
+
+load-module module-waveout sink_name=output source_name=input
+load-module module-null-sink
+
+# Load audio drivers automatically on access
+
+#add-autoload-sink output module-waveout sink_name=output source_name=input
+#add-autoload-source input module-waveout sink_name=output source_name=input
+
+# Load several protocols
+#load-module module-esound-protocol-tcp
+#load-module module-native-protocol-tcp
+#load-module module-simple-protocol-tcp
+#load-module module-cli-protocol-tcp
+
+# Make some devices default
+set-default-sink output
+set-default-source input
+
+.nofail
+
+# Load something to the sample cache
+load-sample x11-bell %WINDIR%\Media\ding.wav
+load-sample-dir-lazy %WINDIR%\Media\*.wav
diff --git a/src/depmod.py b/src/depmod.py
new file mode 100755
index 00000000..7bb223b1
--- /dev/null
+++ b/src/depmod.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+# $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.
+
+import sys, os, string
+
+exported_symbols = {}
+imported_symbols = {}
+
+for fn in sys.argv[1:]:
+ f = os.popen("nm '%s'" % fn, "r")
+
+ imported_symbols[fn] = []
+
+ for line in f:
+ sym_address = line[:7].strip()
+ sym_type = line[9].strip()
+ sym_name = line[11:].strip()
+
+ if sym_name in ('_fini', '_init'):
+ continue
+
+ if sym_type in ('T', 'B', 'R', 'D' 'G', 'S', 'D'):
+ if exported_symbols.has_key(sym_name):
+ sys.stderr.write("CONFLICT: %s defined in both '%s' and '%s'.\n" % (sym_name, fn, exported_symbols[sym_name]))
+ else:
+ exported_symbols[sym_name] = fn
+ elif sym_type in ('U',):
+ if sym_name[:3] == 'pa_':
+ imported_symbols[fn].append(sym_name)
+
+ f.close()
+
+dependencies = {}
+unresolved_symbols = {}
+
+for fn in imported_symbols:
+ dependencies[fn] = []
+
+ for sym in imported_symbols[fn]:
+ if exported_symbols.has_key(sym):
+ if exported_symbols[sym] not in dependencies[fn]:
+ dependencies[fn].append(exported_symbols[sym])
+ else:
+ if unresolved_symbols.has_key(sym):
+ unresolved_symbols[sym].append(fn)
+ else:
+ unresolved_symbols[sym] = [fn]
+
+for sym, files in unresolved_symbols.iteritems():
+ print "WARNING: Unresolved symbol '%s' in %s" % (sym, `files`)
+
+k = dependencies.keys()
+k.sort()
+for fn in k:
+ dependencies[fn].sort()
+ print "%s: %s" % (fn, string.join(dependencies[fn], " "))
diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c
new file mode 100644
index 00000000..48e90e9f
--- /dev/null
+++ b/src/modules/module-alsa-sink.c
@@ -0,0 +1,292 @@
+/* $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>
+
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include "poll.h"
+#endif
+
+#include <asoundlib.h>
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+#include <polypcore/memchunk.h>
+#include <polypcore/sink.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/alsa-util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-alsa-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ALSA Sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ snd_pcm_t *pcm_handle;
+ pa_sink *sink;
+ pa_io_event **io_events;
+ unsigned n_io_events;
+
+ size_t frame_size, fragment_size;
+ pa_memchunk memchunk, silence;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "sink_name",
+ "format",
+ "channels",
+ "rate",
+ "fragments",
+ "fragment_size",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "alsa_output"
+#define DEFAULT_DEVICE "default"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
+}
+
+static void xrun_recovery(struct userdata *u) {
+ assert(u);
+
+ pa_log(__FILE__": *** ALSA-XRUN (playback) ***\n");
+
+ if (snd_pcm_prepare(u->pcm_handle) < 0)
+ pa_log(__FILE__": snd_pcm_prepare() failed\n");
+}
+
+static void do_write(struct userdata *u) {
+ assert(u);
+
+ update_usage(u);
+
+ for (;;) {
+ pa_memchunk *memchunk = NULL;
+ snd_pcm_sframes_t frames;
+
+ if (u->memchunk.memblock)
+ memchunk = &u->memchunk;
+ else {
+ if (pa_sink_render(u->sink, u->fragment_size, &u->memchunk) < 0)
+ memchunk = &u->silence;
+ else
+ memchunk = &u->memchunk;
+ }
+
+ assert(memchunk->memblock && memchunk->memblock->data && memchunk->length && memchunk->memblock->length && (memchunk->length % u->frame_size) == 0);
+
+ if ((frames = snd_pcm_writei(u->pcm_handle, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length / u->frame_size)) < 0) {
+ if (frames == -EAGAIN)
+ return;
+
+ if (frames == -EPIPE) {
+ xrun_recovery(u);
+ continue;
+ }
+
+ pa_log(__FILE__": snd_pcm_writei() failed\n");
+ return;
+ }
+
+ if (memchunk == &u->memchunk) {
+ size_t l = frames * u->frame_size;
+ memchunk->index += l;
+ memchunk->length -= l;
+
+ if (memchunk->length == 0) {
+ pa_memblock_unref(memchunk->memblock);
+ memchunk->memblock = NULL;
+ memchunk->index = memchunk->length = 0;
+ }
+ }
+
+ break;
+ }
+}
+
+static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && a && e);
+
+ if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
+ xrun_recovery(u);
+
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ snd_pcm_sframes_t frames;
+ assert(s && u && u->sink);
+
+ if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
+ pa_log(__FILE__": failed to get delay\n");
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ if (frames < 0)
+ frames = 0;
+
+ r += pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ struct userdata *u = NULL;
+ const char *dev;
+ pa_sample_spec ss;
+ uint32_t periods, fragsize;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ int err;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+ frame_size = pa_frame_size(&ss);
+
+ periods = 8;
+ fragsize = 1024;
+ if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
+ pa_log(__FILE__": failed to parse buffer metrics\n");
+ goto fail;
+ }
+ period_size = fragsize;
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
+ pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
+ pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
+ goto fail;
+ }
+
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
+
+ if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
+ pa_log(__FILE__": failed to obtain file descriptors\n");
+ goto fail;
+ }
+
+ u->frame_size = frame_size;
+ u->fragment_size = period_size;
+
+ pa_log_info(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
+
+ u->silence.memblock = pa_memblock_new(u->silence.length = u->fragment_size, c->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+
+fail:
+
+ if (u)
+ pa__done(c, m);
+
+ goto finish;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->io_events)
+ pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c
new file mode 100644
index 00000000..f03e51ad
--- /dev/null
+++ b/src/modules/module-alsa-source.c
@@ -0,0 +1,278 @@
+/* $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>
+
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include "poll.h"
+#endif
+
+#include <asoundlib.h>
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+#include <polypcore/memchunk.h>
+#include <polypcore/sink.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/alsa-util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-alsa-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ALSA Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("source_name=<name for the source> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ snd_pcm_t *pcm_handle;
+ pa_source *source;
+ pa_io_event **io_events;
+ unsigned n_io_events;
+
+ size_t frame_size, fragment_size;
+ pa_memchunk memchunk;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "source_name",
+ "channels",
+ "rate",
+ "format",
+ "fragments",
+ "fragment_size",
+ NULL
+};
+
+#define DEFAULT_SOURCE_NAME "alsa_input"
+#define DEFAULT_DEVICE "hw:0,0"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void xrun_recovery(struct userdata *u) {
+ assert(u);
+
+ pa_log(__FILE__": *** ALSA-XRUN (capture) ***\n");
+
+ if (snd_pcm_prepare(u->pcm_handle) < 0)
+ pa_log(__FILE__": snd_pcm_prepare() failed\n");
+}
+
+static void do_read(struct userdata *u) {
+ assert(u);
+
+ update_usage(u);
+
+ for (;;) {
+ pa_memchunk post_memchunk;
+ snd_pcm_sframes_t frames;
+ size_t l;
+
+ if (!u->memchunk.memblock) {
+ u->memchunk.memblock = pa_memblock_new(u->memchunk.length = u->fragment_size, u->source->core->memblock_stat);
+ u->memchunk.index = 0;
+ }
+
+ assert(u->memchunk.memblock && u->memchunk.memblock->data && u->memchunk.length && u->memchunk.memblock->length && (u->memchunk.length % u->frame_size) == 0);
+
+ if ((frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) {
+ if (frames == -EAGAIN)
+ return;
+
+ if (frames == -EPIPE) {
+ xrun_recovery(u);
+ continue;
+ }
+
+ pa_log(__FILE__": snd_pcm_readi() failed: %s\n", strerror(-frames));
+ return;
+ }
+
+ l = frames * u->frame_size;
+
+ post_memchunk = u->memchunk;
+ post_memchunk.length = l;
+
+ pa_source_post(u->source, &post_memchunk);
+
+ u->memchunk.index += l;
+ u->memchunk.length -= l;
+
+ if (u->memchunk.length == 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+ }
+
+ break;
+ }
+}
+
+static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && a && e);
+
+ if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
+ xrun_recovery(u);
+
+ do_read(u);
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ snd_pcm_sframes_t frames;
+ assert(s && u && u->source);
+
+ if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
+ pa_log(__FILE__": failed to get delay\n");
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ return pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ struct userdata *u = NULL;
+ const char *dev;
+ pa_sample_spec ss;
+ unsigned periods, fragsize;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ int err;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+ frame_size = pa_frame_size(&ss);
+
+ periods = 12;
+ fragsize = 1024;
+ if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
+ pa_log(__FILE__": failed to parse buffer metrics\n");
+ goto fail;
+ }
+ period_size = fragsize;
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) {
+ pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
+ pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
+ goto fail;
+ }
+
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+
+ u->source->userdata = u;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
+
+ if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
+ pa_log(__FILE__": failed to obtain file descriptors\n");
+ goto fail;
+ }
+
+ u->frame_size = frame_size;
+ u->fragment_size = period_size;
+
+ pa_log(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+
+ snd_pcm_start(u->pcm_handle);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+
+fail:
+
+ if (u)
+ pa__done(c, m);
+
+ goto finish;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->io_events)
+ pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c
new file mode 100644
index 00000000..c782ff8d
--- /dev/null
+++ b/src/modules/module-cli.c
@@ -0,0 +1,88 @@
+/* $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 <unistd.h>
+
+#include <polypcore/module.h>
+#include <polypcore/iochannel.h>
+#include <polypcore/cli.h>
+#include <polypcore/sioman.h>
+#include <polypcore/log.h>
+
+#include "module-cli-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Command line interface")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("No arguments")
+
+static void eof_cb(pa_cli*c, void *userdata) {
+ pa_module *m = userdata;
+ assert(c && m);
+
+ pa_module_unload_request(m);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_iochannel *io;
+ assert(c && m);
+
+ if (c->running_as_daemon) {
+ pa_log_info(__FILE__": Running as daemon so won't load this module.\n");
+ return 0;
+ }
+
+ if (m->argument) {
+ pa_log(__FILE__": module doesn't accept arguments.\n");
+ return -1;
+ }
+
+ if (pa_stdio_acquire() < 0) {
+ pa_log(__FILE__": STDIN/STDUSE already in use.\n");
+ return -1;
+ }
+
+ io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO);
+ assert(io);
+ pa_iochannel_set_noclose(io, 1);
+
+ m->userdata = pa_cli_new(c, io, m);
+ assert(m->userdata);
+
+ pa_cli_set_eof_callback(m->userdata, eof_cb, m);
+
+ return 0;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+ if (c->running_as_daemon == 0) {
+ pa_cli_free(m->userdata);
+ pa_stdio_release();
+ }
+}
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
new file mode 100644
index 00000000..aabb8f28
--- /dev/null
+++ b/src/modules/module-combine.c
@@ -0,0 +1,394 @@
+/* $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 <polypcore/module.h>
+#include <polypcore/llist.h>
+#include <polypcore/sink.h>
+#include <polypcore/sink-input.h>
+#include <polypcore/memblockq.h>
+#include <polypcore/log.h>
+#include <polypcore/util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+#include <polypcore/namereg.h>
+
+#include "module-combine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Combine multiple sinks to one")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> master=<master sink> slaves=<slave sinks> adjust_time=<seconds> resample_method=<method>")
+
+#define DEFAULT_SINK_NAME "combined"
+#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define RENDER_SIZE (1024*10)
+
+#define DEFAULT_ADJUST_TIME 20
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "master",
+ "slaves",
+ "adjust_time",
+ "resample_method",
+ NULL
+};
+
+struct output {
+ struct userdata *userdata;
+ pa_sink_input *sink_input;
+ size_t counter;
+ pa_memblockq *memblockq;
+ pa_usec_t total_latency;
+ PA_LLIST_FIELDS(struct output);
+};
+
+struct userdata {
+ pa_module *module;
+ pa_core *core;
+ pa_sink *sink;
+ unsigned n_outputs;
+ struct output *master;
+ pa_time_event *time_event;
+ uint32_t adjust_time;
+
+ PA_LLIST_HEAD(struct output, outputs);
+};
+
+static void output_free(struct output *o);
+static void clear_up(struct userdata *u);
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
+}
+
+
+static void adjust_rates(struct userdata *u) {
+ struct output *o;
+ pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency;
+ uint32_t base_rate;
+ assert(u && u->sink);
+
+ for (o = u->outputs; o; o = o->next) {
+ uint32_t sink_latency = o->sink_input->sink ? pa_sink_get_latency(o->sink_input->sink) : 0;
+
+ o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input);
+
+ if (sink_latency > max_sink_latency)
+ max_sink_latency = sink_latency;
+
+ if (o->total_latency < min_total_latency)
+ min_total_latency = o->total_latency;
+ }
+
+ assert(min_total_latency != (pa_usec_t) -1);
+
+ target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency;
+
+ pa_log_info(__FILE__": [%s] target latency is %0.0f usec.\n", u->sink->name, (float) target_latency);
+
+ base_rate = u->sink->sample_spec.rate;
+
+ for (o = u->outputs; o; o = o->next) {
+ uint32_t r = base_rate;
+
+ if (o->total_latency < target_latency)
+ r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/ 1000000);
+ else if (o->total_latency > target_latency)
+ r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/ 1000000);
+
+ if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1))
+ pa_log_warn(__FILE__": [%s] sample rates too different, not adjusting (%u vs. %u).\n", o->sink_input->name, base_rate, r);
+ else {
+ pa_log_info(__FILE__": [%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.\n", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency);
+ pa_sink_input_set_rate(o->sink_input, r);
+ }
+ }
+}
+
+static void request_memblock(struct userdata *u) {
+ pa_memchunk chunk;
+ struct output *o;
+ assert(u && u->sink);
+
+ update_usage(u);
+
+ if (pa_sink_render(u->sink, RENDER_SIZE, &chunk) < 0)
+ return;
+
+ for (o = u->outputs; o; o = o->next)
+ pa_memblockq_push_align(o->memblockq, &chunk, 0);
+
+ pa_memblock_unref(chunk.memblock);
+}
+
+static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval n;
+ assert(u && a && u->time_event == e);
+
+ adjust_rates(u);
+
+ pa_gettimeofday(&n);
+ n.tv_sec += u->adjust_time;
+ u->sink->core->mainloop->time_restart(e, &n);
+}
+
+static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input && chunk);
+
+ if (pa_memblockq_peek(o->memblockq, chunk) >= 0)
+ return 0;
+
+ /* Try harder */
+ request_memblock(o->userdata);
+
+ return pa_memblockq_peek(o->memblockq, chunk);
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input && chunk && length);
+
+ pa_memblockq_drop(o->memblockq, chunk, length);
+ o->counter += length;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input);
+ pa_module_unload_request(o->userdata->module);
+ clear_up(o->userdata);
+}
+
+static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &i->sample_spec);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink && u->master);
+
+ return pa_sink_input_get_latency(u->master->sink_input);
+}
+
+static struct output *output_new(struct userdata *u, pa_sink *sink, int resample_method) {
+ struct output *o = NULL;
+ char t[256];
+ assert(u && sink && u->sink);
+
+ o = pa_xmalloc(sizeof(struct output));
+ o->userdata = u;
+
+ o->counter = 0;
+ o->memblockq = pa_memblockq_new(MEMBLOCKQ_MAXLENGTH, MEMBLOCKQ_MAXLENGTH, pa_frame_size(&u->sink->sample_spec), 0, 0, sink->core->memblock_stat);
+
+ snprintf(t, sizeof(t), "%s: output #%u", u->sink->name, u->n_outputs+1);
+ if (!(o->sink_input = pa_sink_input_new(sink, __FILE__, t, &u->sink->sample_spec, &u->sink->channel_map, 1, resample_method)))
+ goto fail;
+
+ o->sink_input->get_latency = sink_input_get_latency_cb;
+ o->sink_input->peek = sink_input_peek_cb;
+ o->sink_input->drop = sink_input_drop_cb;
+ o->sink_input->kill = sink_input_kill_cb;
+ o->sink_input->userdata = o;
+ o->sink_input->owner = u->module;
+
+ PA_LLIST_PREPEND(struct output, u->outputs, o);
+ u->n_outputs++;
+ return o;
+
+fail:
+
+ if (o) {
+ if (o->sink_input) {
+ pa_sink_input_disconnect(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ }
+
+ if (o->memblockq)
+ pa_memblockq_free(o->memblockq);
+
+ pa_xfree(o);
+ }
+
+ return NULL;
+}
+
+static void output_free(struct output *o) {
+ assert(o);
+ PA_LLIST_REMOVE(struct output, o->userdata->outputs, o);
+ o->userdata->n_outputs--;
+ pa_memblockq_free(o->memblockq);
+ pa_sink_input_disconnect(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ pa_xfree(o);
+}
+
+static void clear_up(struct userdata *u) {
+ struct output *o;
+ assert(u);
+
+ if (u->time_event) {
+ u->core->mainloop->time_free(u->time_event);
+ u->time_event = NULL;
+ }
+
+ while ((o = u->outputs))
+ output_free(o);
+
+ u->master = NULL;
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ const char *master_name, *slaves, *rm;
+ pa_sink *master_sink;
+ char *n = NULL;
+ const char*split_state;
+ struct timeval tv;
+ int resample_method = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) {
+ if ((resample_method = pa_parse_resample_method(rm)) < 0) {
+ pa_log(__FILE__": invalid resample method '%s'\n", rm);
+ goto fail;
+ }
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ m->userdata = u;
+ u->sink = NULL;
+ u->n_outputs = 0;
+ u->master = NULL;
+ u->module = m;
+ u->core = c;
+ u->time_event = NULL;
+ u->adjust_time = DEFAULT_ADJUST_TIME;
+ PA_LLIST_HEAD_INIT(struct output, u->outputs);
+
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
+ pa_log(__FILE__": failed to parse adjust_time value\n");
+ goto fail;
+ }
+
+ if (!(master_name = pa_modargs_get_value(ma, "master", NULL)) || !(slaves = pa_modargs_get_value(ma, "slaves", NULL))) {
+ pa_log(__FILE__": no master or slave sinks specified\n");
+ goto fail;
+ }
+
+ if (!(master_sink = pa_namereg_get(c, master_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": invalid master sink '%s'\n", master_name);
+ goto fail;
+ }
+
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &master_sink->sample_spec, &master_sink->channel_map))) {
+ pa_log(__FILE__": failed to create sink\n");
+ goto fail;
+ }
+
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Combined sink");
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+
+ if (!(u->master = output_new(u, master_sink, resample_method))) {
+ pa_log(__FILE__": failed to create master sink input on sink '%s'.\n", u->sink->name);
+ goto fail;
+ }
+
+ split_state = NULL;
+ while ((n = pa_split(slaves, ",", &split_state))) {
+ pa_sink *slave_sink;
+
+ if (!(slave_sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": invalid slave sink '%s'\n", n);
+ goto fail;
+ }
+
+ pa_xfree(n);
+
+ if (!output_new(u, slave_sink, resample_method)) {
+ pa_log(__FILE__": failed to create slave sink input on sink '%s'.\n", slave_sink->name);
+ goto fail;
+ }
+ }
+
+ if (u->n_outputs <= 1)
+ pa_log_warn(__FILE__": WARNING: no slave sinks specified.\n");
+
+ if (u->adjust_time > 0) {
+ pa_gettimeofday(&tv);
+ tv.tv_sec += u->adjust_time;
+ u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
+ }
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa_xfree(n);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ clear_up(u);
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4
new file mode 100644
index 00000000..8f10d659
--- /dev/null
+++ b/src/modules/module-defs.h.m4
@@ -0,0 +1,29 @@
+dnl $Id$
+changecom(`/*', `*/')dnl
+define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl
+define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl
+define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl
+define(`incmacro', `foo'c_macro`symdeffoo')dnl
+define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl
+#ifndef incmacro
+#define incmacro
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+
+gen_symbol(pa__init)
+gen_symbol(pa__done)
+gen_symbol(pa__get_author)
+gen_symbol(pa__get_description)
+gen_symbol(pa__get_usage)
+gen_symbol(pa__get_version)
+
+int pa__init(struct pa_core *c, struct pa_module*m);
+void pa__done(struct pa_core *c, struct pa_module*m);
+
+const char* pa__get_author(void);
+const char* pa__get_description(void);
+const char* pa__get_usage(void);
+const char* pa__get_version(void);
+
+#endif
diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c
new file mode 100644
index 00000000..e325b22c
--- /dev/null
+++ b/src/modules/module-detect.c
@@ -0,0 +1,227 @@
+/* $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 <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-detect-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("just-one=<boolean>")
+
+static const char *endswith(const char *haystack, const char *needle) {
+ size_t l, m;
+ const char *p;
+
+ if ((l = strlen(haystack)) < (m = strlen(needle)))
+ return NULL;
+
+ if (strcmp(p = haystack + l - m, needle))
+ return NULL;
+
+ return p;
+}
+
+#ifdef HAVE_ALSA
+static int detect_alsa(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, n_sink = 0, n_source = 0;
+
+ if (!(f = fopen("/proc/asound/devices", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error(__FILE__": open(\"/proc/asound/devices\") failed: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device, subdevice;
+ int is_sink;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (endswith(line, "digital audio playback"))
+ is_sink = 1;
+ else if (endswith(line, "digital audio capture"))
+ is_sink = 0;
+ else
+ continue;
+
+ if (just_one && is_sink && n_sink >= 1)
+ continue;
+
+ if (just_one && !is_sink && n_source >= 1)
+ continue;
+
+ if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2)
+ continue;
+
+ /* Only one sink per device */
+ if (subdevice != 0)
+ continue;
+
+ snprintf(args, sizeof(args), "device=hw:%u,0", device);
+ if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
+ continue;
+
+ n++;
+
+ if (is_sink)
+ n_sink++;
+ else
+ n_source++;
+ }
+
+ fclose(f);
+
+ return n;
+}
+#endif
+
+#ifdef HAVE_OSS
+static int detect_oss(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, b = 0;
+
+ if (!(f = fopen("/dev/sndstat", "r")) &&
+ !(f = fopen("/proc/sndstat", "r")) &&
+ !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error(__FILE__": failed to open OSS sndstat device: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (!b) {
+ b = strcmp(line, "Audio devices:") == 0;
+ continue;
+ }
+
+ if (line[0] == 0)
+ break;
+
+ if (sscanf(line, "%u: ", &device) != 1)
+ continue;
+
+ if (device == 0)
+ snprintf(args, sizeof(args), "device=/dev/dsp");
+ else
+ snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
+
+ if (!pa_module_load(c, "module-oss", args))
+ continue;
+
+ n++;
+
+ if (just_one)
+ break;
+ }
+
+ fclose(f);
+ return n;
+}
+#endif
+
+int pa__init(pa_core *c, pa_module*m) {
+ int just_one = 0, n = 0;
+ pa_modargs *ma;
+
+ static const char* const valid_modargs[] = {
+ "just-one",
+ NULL
+ };
+
+ assert(c);
+ assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) {
+ pa_log(__FILE__": just_one= expects a boolean argument.\n");
+ goto fail;
+ }
+
+#if HAVE_ALSA
+ if ((n = detect_alsa(c, just_one)) <= 0)
+#endif
+#if HAVE_OSS
+ if ((n = detect_oss(c, just_one)) <= 0)
+#endif
+ {
+ pa_log_warn(__FILE__": failed to detect any sound hardware.\n");
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": loaded %i modules.\n", n);
+
+ /* We were successful and can unload ourselves now. */
+ pa_module_unload_request(m);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+
+void pa__done(pa_core *c, pa_module*m) {
+ /* NOP */
+}
+
diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c
new file mode 100644
index 00000000..5c656be9
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnfd.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 <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+
+#include "module-esound-compat-spawnfd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation")
+PA_MODULE_USAGE("fd=<file descriptor>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "fd",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1, fd = -1;
+ char x = 1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_s32(ma, "fd", &fd) < 0 ||
+ fd < 0) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (pa_loop_write(fd, &x, sizeof(x)) != sizeof(x))
+ pa_log(__FILE__": WARNING: write(%u, 1, 1) failed: %s\n", fd, strerror(errno));
+
+ close(fd);
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+}
+
+
diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c
new file mode 100644
index 00000000..5daa1297
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnpid.c
@@ -0,0 +1,79 @@
+
+/***
+ 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 <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+
+#include "module-esound-compat-spawnpid-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation")
+PA_MODULE_USAGE("pid=<process id>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "pid",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ uint32_t pid = 0;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_u32(ma, "pid", &pid) < 0 ||
+ !pid) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (kill(pid, SIGUSR1) < 0)
+ pa_log(__FILE__": WARNING: kill(%u) failed: %s\n", pid, strerror(errno));
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+}
+
+
diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c
new file mode 100644
index 00000000..4f724811
--- /dev/null
+++ b/src/modules/module-esound-sink.c
@@ -0,0 +1,427 @@
+/* $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 <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/esound.h>
+#include <polypcore/authkey.h>
+
+#include "module-esound-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND Sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> server=<address> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_SINK_NAME "esound_output"
+
+struct userdata {
+ pa_core *core;
+
+ pa_sink *sink;
+ pa_iochannel *io;
+ pa_socket_client *client;
+
+ pa_defer_event *defer_event;
+
+ pa_memchunk memchunk;
+ pa_module *module;
+
+ void *write_data;
+ size_t write_length, write_index;
+
+ void *read_data;
+ size_t read_length, read_index;
+
+ enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state;
+
+ pa_usec_t latency;
+
+ esd_format_t format;
+ int32_t rate;
+};
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void cancel(struct userdata *u) {
+ assert(u);
+
+ u->state = STATE_DEAD;
+
+ if (u->io) {
+ pa_iochannel_free(u->io);
+ u->io = NULL;
+ }
+
+ if (u->defer_event) {
+ u->core->mainloop->defer_free(u->defer_event);
+ u->defer_event = NULL;
+ }
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+
+ if (u->module) {
+ pa_module_unload_request(u->module);
+ u->module = NULL;
+ }
+}
+
+static int do_write(struct userdata *u) {
+ ssize_t r;
+ assert(u);
+
+ if (!pa_iochannel_is_writable(u->io))
+ return 0;
+
+ if (u->write_data) {
+ assert(u->write_index < u->write_length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ u->write_index += r;
+ assert(u->write_index <= u->write_length);
+
+ if (u->write_index == u->write_length) {
+ free(u->write_data);
+ u->write_data = NULL;
+ u->write_index = u->write_length = 0;
+ }
+ } else if (u->state == STATE_RUNNING) {
+ pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
+
+ if (!u->memchunk.length)
+ if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0)
+ return 0;
+
+ assert(u->memchunk.memblock && u->memchunk.length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int handle_response(struct userdata *u) {
+ assert(u);
+
+ switch (u->state) {
+ case STATE_AUTH:
+ assert(u->read_length == sizeof(int32_t));
+
+ /* Process auth data */
+ if (!*(int32_t*) u->read_data) {
+ pa_log(__FILE__": Authentication failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Request latency data */
+ assert(!u->write_data);
+ *(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY;
+
+ u->write_index = 0;
+ u->state = STATE_LATENCY;
+
+ /* Space for next response */
+ assert(u->read_length >= sizeof(int32_t));
+ u->read_index = 0;
+ u->read_length = sizeof(int32_t);
+
+ break;
+
+ case STATE_LATENCY: {
+ int32_t *p;
+ assert(u->read_length == sizeof(int32_t));
+
+ /* Process latency info */
+ u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100);
+ if (u->latency > 10000000) {
+ pa_log(__FILE__": WARNING! Invalid latency information received from server\n");
+ u->latency = 0;
+ }
+
+ /* Create stream */
+ assert(!u->write_data);
+ p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX);
+ *(p++) = ESD_PROTO_STREAM_PLAY;
+ *(p++) = u->format;
+ *(p++) = u->rate;
+ pa_strlcpy((char*) p, "Polypaudio Tunnel", ESD_NAME_MAX);
+
+ u->write_index = 0;
+ u->state = STATE_RUNNING;
+
+ /* Don't read any further */
+ pa_xfree(u->read_data);
+ u->read_data = NULL;
+ u->read_index = u->read_length = 0;
+
+ break;
+ }
+
+ default:
+ abort();
+ }
+
+ return 0;
+}
+
+static int do_read(struct userdata *u) {
+ assert(u);
+
+ if (!pa_iochannel_is_readable(u->io))
+ return 0;
+
+ if (u->state == STATE_AUTH || u->state == STATE_LATENCY) {
+ ssize_t r;
+
+ if (!u->read_data)
+ return 0;
+
+ assert(u->read_index < u->read_length);
+
+ if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) {
+ pa_log(__FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
+ cancel(u);
+ return -1;
+ }
+
+ u->read_index += r;
+ assert(u->read_index <= u->read_length);
+
+ if (u->read_index == u->read_length)
+ return handle_response(u);
+ }
+
+ return 0;
+}
+
+static void do_work(struct userdata *u) {
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer_event, 0);
+
+ if (do_read(u) < 0 || do_write(u) < 0)
+ cancel(u);
+}
+
+static void notify_cb(pa_sink*s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ if (pa_iochannel_is_writable(u->io))
+ u->core->mainloop->defer_enable(u->defer_event, 1);
+}
+
+static pa_usec_t get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ return
+ u->latency +
+ (u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0);
+}
+
+static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_work(u);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_work(u);
+}
+
+static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log(__FILE__": connection failed: %s\n", strerror(errno));
+ cancel(u);
+ return;
+ }
+
+ u->io = io;
+ pa_iochannel_set_callback(u->io, io_callback, u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ const char *p;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ if ((ss.format != PA_SAMPLE_U8 && ss.format != PA_SAMPLE_S16NE) ||
+ (ss.channels > 2)) {
+ pa_log(__FILE__": esound sample type support is limited to mono/stereo and U8 or S16NE sample data\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+ u->format =
+ (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) |
+ (ss.channels == 2 ? ESD_STEREO : ESD_MONO);
+ u->rate = ss.rate;
+ u->sink = NULL;
+ u->client = NULL;
+ u->io = NULL;
+ u->read_data = u->write_data = NULL;
+ u->read_index = u->write_index = u->read_length = u->write_length = 0;
+ u->state = STATE_AUTH;
+ u->latency = 0;
+
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create sink.\n");
+ goto fail;
+ }
+
+ if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", ESD_UNIX_SOCKET_NAME), ESD_DEFAULT_PORT))) {
+ pa_log(__FILE__": failed to connect to server.\n");
+ goto fail;
+ }
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+ /* Prepare the initial request */
+ u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t));
+ if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) {
+ pa_log(__FILE__": failed to load cookie\n");
+ goto fail;
+ }
+ *(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY;
+
+ /* Reserve space for the response */
+ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t));
+
+ u->sink->notify = notify_cb;
+ u->sink->get_latency = get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Esound sink '%s'", p);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+
+ u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
+ c->mainloop->defer_enable(u->defer_event, 0);
+
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ u->module = NULL;
+ cancel(u);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->client)
+ pa_socket_client_unref(u->client);
+
+ pa_xfree(u->read_data);
+ pa_xfree(u->write_data);
+
+ pa_xfree(u);
+}
+
+
+
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
new file mode 100644
index 00000000..ea8a2bd2
--- /dev/null
+++ b/src/modules/module-lirc.c
@@ -0,0 +1,241 @@
+/* $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 <unistd.h>
+#include <string.h>
+#include <lirc/lirc_client.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/log.h>
+#include <polypcore/namereg.h>
+#include <polypcore/sink.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+
+#include "module-lirc-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("LIRC volume control")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>")
+
+static const char* const valid_modargs[] = {
+ "config",
+ "sink",
+ "appname",
+ NULL,
+};
+
+struct userdata {
+ int lirc_fd;
+ pa_io_event *io;
+ struct lirc_config *config;
+ char *sink_name;
+ pa_module *module;
+ float mute_toggle_save;
+};
+
+static int lirc_in_use = 0;
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+ char *name = NULL, *code = NULL;
+ assert(io);
+ assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log(__FILE__": lost connection to LIRC daemon.\n");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ char *c;
+
+ if (lirc_nextcode(&code) != 0 || !code) {
+ pa_log(__FILE__": lirc_nextcode() failed.\n");
+ goto fail;
+ }
+
+ c = pa_xstrdup(code);
+ c[strcspn(c, "\n\r")] = 0;
+ pa_log_debug(__FILE__": raw IR code '%s'\n", c);
+ pa_xfree(c);
+
+ while (lirc_code2char(u->config, code, &name) == 0 && name) {
+ enum { INVALID, UP, DOWN, MUTE, RESET, MUTE_TOGGLE } volchange = INVALID;
+
+ pa_log_info(__FILE__": translated IR code '%s'\n", name);
+
+ if (strcasecmp(name, "volume-up") == 0)
+ volchange = UP;
+ else if (strcasecmp(name, "volume-down") == 0)
+ volchange = DOWN;
+ else if (strcasecmp(name, "mute") == 0)
+ volchange = MUTE;
+ else if (strcasecmp(name, "mute-toggle") == 0)
+ volchange = MUTE_TOGGLE;
+ else if (strcasecmp(name, "reset") == 0)
+ volchange = RESET;
+
+ if (volchange == INVALID)
+ pa_log_warn(__FILE__": recieved unknown IR code '%s'\n", name);
+ else {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
+ else {
+ pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
+ pa_cvolume cv;
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ v += PA_VOLUME_NORM/20;
+ break;
+
+ case DOWN:
+ if (v > DELTA)
+ v -= DELTA;
+ else
+ v = PA_VOLUME_MUTED;
+
+ break;
+
+ case MUTE:
+ v = PA_VOLUME_MUTED;
+ break;
+
+ case RESET:
+ v = PA_VOLUME_NORM;
+ break;
+
+ case MUTE_TOGGLE: {
+
+ if (v > 0) {
+ u->mute_toggle_save = v;
+ v = PA_VOLUME_MUTED;
+ } else
+ v = u->mute_toggle_save;
+ }
+ default:
+ ;
+ }
+
+ pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
+ pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
+ }
+ }
+ }
+ }
+
+ free(code);
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+
+ free(code);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ assert(c && m);
+
+ if (lirc_in_use) {
+ pa_log(__FILE__": module-lirc may no be loaded twice.\n");
+ return -1;
+ }
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->module = m;
+ u->io = NULL;
+ u->config = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->lirc_fd = -1;
+ u->mute_toggle_save = 0;
+
+ if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "polypaudio"), 1)) < 0) {
+ pa_log(__FILE__": lirc_init() failed.\n");
+ goto fail;
+ }
+
+ if (lirc_readconfig((char*) pa_modargs_get_value(ma, "config", NULL), &u->config, NULL) < 0) {
+ pa_log(__FILE__": lirc_readconfig() failed.\n");
+ goto fail;
+ }
+
+ u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
+
+ lirc_in_use = 1;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c);
+ assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->config)
+ lirc_freeconfig(u->config);
+
+ if (u->lirc_fd >= 0)
+ lirc_deinit();
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+
+ lirc_in_use = 0;
+}
diff --git a/src/modules/module-match.c b/src/modules/module-match.c
new file mode 100644
index 00000000..10ceb75e
--- /dev/null
+++ b/src/modules/module-match.c
@@ -0,0 +1,235 @@
+/* $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 <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/sink-input.h>
+
+#include "module-match-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Sink input matching module")
+PA_MODULE_USAGE("table=<filename>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+#define WHITESPACE "\n\r \t"
+
+#ifndef DEFAULT_CONFIG_DIR
+#define DEFAULT_CONFIG_DIR "/etc/polypaudio"
+#endif
+
+#define DEFAULT_MATCH_TABLE_FILE DEFAULT_CONFIG_DIR"/match.table"
+#define DEFAULT_MATCH_TABLE_FILE_USER ".polypaudio/match.table"
+
+static const char* const valid_modargs[] = {
+ "table",
+ NULL,
+};
+
+struct rule {
+ regex_t regex;
+ pa_volume_t volume;
+ struct rule *next;
+};
+
+struct userdata {
+ struct rule *rules;
+ pa_subscription *subscription;
+};
+
+static int load_rules(struct userdata *u, const char *filename) {
+ FILE *f;
+ int n = 0;
+ int ret = -1;
+ struct rule *end = NULL;
+ char *fn = NULL;
+
+ f = filename ?
+ fopen(fn = pa_xstrdup(filename), "r") :
+ pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
+
+ if (!f) {
+ pa_log(__FILE__": failed to open file '%s': %s\n", fn, strerror(errno));
+ goto finish;
+ }
+
+ while (!feof(f)) {
+ char *d, *v;
+ pa_volume_t volume;
+ uint32_t k;
+ regex_t regex;
+ char ln[256];
+ struct rule *rule;
+
+ if (!fgets(ln, sizeof(ln), f))
+ break;
+
+ n++;
+
+ pa_strip_nl(ln);
+
+ if (ln[0] == '#' || !*ln )
+ continue;
+
+ d = ln+strcspn(ln, WHITESPACE);
+ v = d+strspn(d, WHITESPACE);
+
+
+ if (!*v) {
+ pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words\n", filename, n);
+ goto finish;
+ }
+
+ *d = 0;
+ if (pa_atou(v, &k) < 0) {
+ pa_log(__FILE__": [%s:%u] failed to parse volume\n", filename, n);
+ goto finish;
+ }
+
+ volume = (pa_volume_t) k;
+
+
+ if (regcomp(&regex, ln, REG_EXTENDED|REG_NOSUB) != 0) {
+ pa_log(__FILE__": [%s:%u] invalid regular expression\n", filename, n);
+ goto finish;
+ }
+
+ rule = pa_xmalloc(sizeof(struct rule));
+ rule->regex = regex;
+ rule->volume = volume;
+ rule->next = NULL;
+
+ if (end)
+ end->next = rule;
+ else
+ u->rules = rule;
+ end = rule;
+
+ *d = 0;
+ }
+
+ ret = 0;
+
+finish:
+ if (f)
+ fclose(f);
+
+ if (fn)
+ pa_xfree(fn);
+
+ return ret;
+}
+
+static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sink_input *si;
+ struct rule *r;
+ assert(c && u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
+ return;
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ if (!si->name)
+ return;
+
+ for (r = u->rules; r; r = r->next) {
+ if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
+ pa_cvolume cv;
+ pa_log_debug(__FILE__": changing volume of sink input '%s' to 0x%03x\n", si->name, r->volume);
+ pa_cvolume_set(&cv, r->volume, si->sample_spec.channels);
+ pa_sink_input_set_volume(si, &cv);
+ }
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->rules = NULL;
+ u->subscription = NULL;
+ m->userdata = u;
+
+ if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
+ goto fail;
+
+ u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata* u;
+ struct rule *r, *n;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ for (r = u->rules; r; r = n) {
+ n = r->next;
+
+ regfree(&r->regex);
+ pa_xfree(r);
+ }
+
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
new file mode 100644
index 00000000..b60f786d
--- /dev/null
+++ b/src/modules/module-mmkbd-evdev.c
@@ -0,0 +1,250 @@
+/* $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 <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <linux/input.h>
+
+#include <polypcore/module.h>
+#include <polypcore/log.h>
+#include <polypcore/namereg.h>
+#include <polypcore/sink.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+
+#include "module-mmkbd-evdev-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("device=<evdev device> sink=<sink name>")
+
+#define DEFAULT_DEVICE "/dev/input/event0"
+
+/*
+ * This isn't defined in older kernel headers and there is no way of
+ * detecting it.
+ */
+struct _input_id {
+ __u16 bustype;
+ __u16 vendor;
+ __u16 product;
+ __u16 version;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "sink",
+ NULL,
+};
+
+struct userdata {
+ int fd;
+ pa_io_event *io;
+ char *sink_name;
+ pa_module *module;
+ float mute_toggle_save;
+};
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+ assert(io);
+ assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log(__FILE__": lost connection to evdev device.\n");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ struct input_event ev;
+
+ if (pa_loop_read(u->fd, &ev, sizeof(ev)) <= 0) {
+ pa_log(__FILE__": failed to read from event device: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
+ enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
+
+ pa_log_debug(__FILE__": key code=%u, value=%u\n", ev.code, ev.value);
+
+ switch (ev.code) {
+ case KEY_VOLUMEDOWN: volchange = DOWN; break;
+ case KEY_VOLUMEUP: volchange = UP; break;
+ case KEY_MUTE: volchange = MUTE_TOGGLE; break;
+ }
+
+ if (volchange != INVALID) {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
+ else {
+ pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
+ pa_cvolume cv;
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ v += DELTA;
+ break;
+
+ case DOWN:
+ if (v > DELTA)
+ v -= DELTA;
+ else
+ v = PA_VOLUME_MUTED;
+
+ break;
+
+ case MUTE_TOGGLE: {
+
+ if (v > 0) {
+ u->mute_toggle_save = v;
+ v = PA_VOLUME_MUTED;
+ } else
+ v = u->mute_toggle_save;
+ }
+ default:
+ ;
+ }
+
+ pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
+ pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
+ }
+ }
+ }
+ }
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ int version;
+ struct _input_id input_id;
+ char name[256];
+ uint8_t evtype_bitmask[EV_MAX/8 + 1];
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->module = m;
+ u->io = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->fd = -1;
+ u->mute_toggle_save = 0;
+
+ if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) {
+ pa_log(__FILE__": failed to open evdev device: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ioctl(u->fd, EVIOCGVERSION, &version) < 0) {
+ pa_log(__FILE__": EVIOCGVERSION failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev driver version %i.%i.%i\n", version >> 16, (version >> 8) & 0xff, version & 0xff);
+
+ if(ioctl(u->fd, EVIOCGID, &input_id)) {
+ pa_log(__FILE__": EVIOCGID failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev vendor 0x%04hx product 0x%04hx version 0x%04hx bustype %u\n",
+ input_id.vendor, input_id.product, input_id.version, input_id.bustype);
+
+ if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+ pa_log(__FILE__": EVIOCGNAME failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev device name: %s\n", name);
+
+ memset(evtype_bitmask, 0, sizeof(evtype_bitmask));
+ if (ioctl(u->fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) {
+ pa_log(__FILE__": EVIOCGBIT failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (!test_bit(EV_KEY, evtype_bitmask)) {
+ pa_log(__FILE__": device has no keys.\n");
+ goto fail;
+ }
+
+ u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c);
+ assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->fd >= 0)
+ close(u->fd);
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c
new file mode 100644
index 00000000..abc531b3
--- /dev/null
+++ b/src/modules/module-native-protocol-fd.c
@@ -0,0 +1,85 @@
+/* $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 <unistd.h>
+
+#include <polypcore/module.h>
+#include <polypcore/iochannel.h>
+#include <polypcore/modargs.h>
+#include <polypcore/protocol-native.h>
+#include <polypcore/log.h>
+
+#include "module-native-protocol-fd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Native protocol autospawn helper")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "fd",
+ "public",
+ "cookie",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_iochannel *io;
+ pa_modargs *ma;
+ int fd, r = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto finish;
+ }
+
+ if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) {
+ pa_log(__FILE__": invalid file descriptor.\n");
+ goto finish;
+ }
+
+ io = pa_iochannel_new(c->mainloop, fd, fd);
+
+ if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) {
+ pa_iochannel_free(io);
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return r;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+ pa_protocol_native_free(m->userdata);
+}
diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
new file mode 100644
index 00000000..5731a403
--- /dev/null
+++ b/src/modules/module-null-sink.c
@@ -0,0 +1,150 @@
+/* $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 <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-null-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Clocked NULL sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name of sink>")
+
+#define DEFAULT_SINK_NAME "null"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+ pa_time_event *time_event;
+ size_t block_size;
+};
+
+static const char* const valid_modargs[] = {
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ pa_memchunk chunk;
+ struct timeval ntv = *tv;
+ size_t l;
+
+ assert(u);
+
+ if (pa_sink_render(u->sink, u->block_size, &chunk) >= 0) {
+ l = chunk.length;
+ pa_memblock_unref(chunk.memblock);
+ } else
+ l = u->block_size;
+
+ pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec));
+ m->time_restart(e, &ntv);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ struct timeval tv;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification.\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create sink.\n");
+ goto fail;
+ }
+
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("NULL sink");
+
+ pa_gettimeofday(&tv);
+ u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
+
+ u->block_size = pa_bytes_per_second(&ss) / 10;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+
+ u->core->mainloop->time_free(u->time_event);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-oss-mmap.c b/src/modules/module-oss-mmap.c
new file mode 100644
index 00000000..6986b03c
--- /dev/null
+++ b/src/modules/module-oss-mmap.c
@@ -0,0 +1,426 @@
+/* $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 <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/mman.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/oss-util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-oss-mmap-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("OSS Sink/Source (mmap)")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_core *core;
+ pa_sample_spec sample_spec;
+
+ size_t in_fragment_size, out_fragment_size, in_fragments, out_fragments, out_fill;
+
+ int fd;
+
+ void *in_mmap, *out_mmap;
+ size_t in_mmap_length, out_mmap_length;
+
+ pa_io_event *io_event;
+
+ pa_memblock **in_memblocks, **out_memblocks;
+ unsigned out_current, in_current;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "oss_output"
+#define DEFAULT_SOURCE_NAME "oss_input"
+#define DEFAULT_DEVICE "/dev/dsp"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void out_fill_memblocks(struct userdata *u, unsigned n) {
+ assert(u && u->out_memblocks);
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (u->out_memblocks[u->out_current])
+ pa_memblock_unref_fixed(u->out_memblocks[u->out_current]);
+
+ chunk.memblock = u->out_memblocks[u->out_current] = pa_memblock_new_fixed((uint8_t*)u->out_mmap+u->out_fragment_size*u->out_current, u->out_fragment_size, 1, u->core->memblock_stat);
+ assert(chunk.memblock);
+ chunk.length = chunk.memblock->length;
+ chunk.index = 0;
+
+ pa_sink_render_into_full(u->sink, &chunk);
+
+ u->out_current++;
+ while (u->out_current >= u->out_fragments)
+ u->out_current -= u->out_fragments;
+
+ n--;
+ }
+}
+
+static void do_write(struct userdata *u) {
+ struct count_info info;
+ assert(u && u->sink);
+
+ update_usage(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETOPTR: %s\n", strerror(errno));
+ return;
+ }
+
+ u->out_fill = (u->out_fragment_size * u->out_fragments) - (info.ptr % u->out_fragment_size);
+
+ if (!info.blocks)
+ return;
+
+ out_fill_memblocks(u, info.blocks);
+}
+
+static void in_post_memblocks(struct userdata *u, unsigned n) {
+ assert(u && u->in_memblocks);
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (!u->in_memblocks[u->in_current]) {
+ chunk.memblock = u->in_memblocks[u->in_current] = pa_memblock_new_fixed((uint8_t*) u->in_mmap+u->in_fragment_size*u->in_current, u->in_fragment_size, 1, u->core->memblock_stat);
+ chunk.length = chunk.memblock->length;
+ chunk.index = 0;
+
+ pa_source_post(u->source, &chunk);
+ }
+
+ u->in_current++;
+ while (u->in_current >= u->in_fragments)
+ u->in_current -= u->in_fragments;
+
+ n--;
+ }
+}
+
+static void in_clear_memblocks(struct userdata*u, unsigned n) {
+ unsigned i = u->in_current;
+ assert(u && u->in_memblocks);
+
+ if (n > u->in_fragments)
+ n = u->in_fragments;
+
+ while (n > 0) {
+ if (u->in_memblocks[i]) {
+ pa_memblock_unref_fixed(u->in_memblocks[i]);
+ u->in_memblocks[i] = NULL;
+ }
+
+ i++;
+ while (i >= u->in_fragments)
+ i -= u->in_fragments;
+
+ n--;
+ }
+}
+
+static void do_read(struct userdata *u) {
+ struct count_info info;
+ assert(u && u->source);
+
+ update_usage(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETIPTR: %s\n", strerror(errno));
+ return;
+ }
+
+ if (!info.blocks)
+ return;
+
+ in_post_memblocks(u, info.blocks);
+ in_clear_memblocks(u, u->in_fragments/2);
+}
+
+static void io_callback(pa_mainloop_api *m, pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert (u && u->core->mainloop == m && u->io_event == e);
+
+ if (f & PA_IO_EVENT_INPUT)
+ do_read(u);
+ if (f & PA_IO_EVENT_OUTPUT)
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ do_write(u);
+ return pa_bytes_to_usec(u->out_fill, &s->sample_spec);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct audio_buf_info info;
+ struct userdata *u = NULL;
+ const char *p;
+ int nfrags, frag_size;
+ int mode, caps;
+ int enable_bits = 0, zero = 0;
+ int playback = 1, record = 1;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ m->userdata = u = pa_xmalloc0(sizeof(struct userdata));
+ u->module = m;
+ u->fd = -1;
+ u->core = c;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric arguments.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ nfrags = 12;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ u->sample_spec = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &u->sample_spec) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((u->fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0)
+ goto fail;
+
+ if (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_REALTIME) || !(caps & DSP_CAP_TRIGGER)) {
+ pa_log(__FILE__": OSS device not mmap capable.\n");
+ goto fail;
+ }
+
+ pa_log(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (nfrags >= 2 && frag_size >= 1)
+ if (pa_oss_set_fragments(u->fd, nfrags, frag_size) < 0)
+ goto fail;
+
+ if (pa_oss_auto_format(u->fd, &u->sample_spec) < 0)
+ goto fail;
+
+ if (mode != O_WRONLY) {
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETISPACE: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ u->in_mmap_length = (u->in_fragment_size = info.fragsize) * (u->in_fragments = info.fragstotal);
+
+ if ((u->in_mmap = mmap(NULL, u->in_mmap_length, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ if (mode == O_RDWR) {
+ pa_log(__FILE__": mmap failed for input. Changing to O_WRONLY mode.\n");
+ mode = O_WRONLY;
+ } else {
+ pa_log(__FILE__": mmap(): %s\n", strerror(errno));
+ goto fail;
+ }
+ } else {
+
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &u->sample_spec, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
+
+ u->in_memblocks = pa_xmalloc0(sizeof(pa_memblock *)*u->in_fragments);
+
+ enable_bits |= PCM_ENABLE_INPUT;
+ }
+ }
+
+ if (mode != O_RDONLY) {
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETOSPACE: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ u->out_mmap_length = (u->out_fragment_size = info.fragsize) * (u->out_fragments = info.fragstotal);
+
+ if ((u->out_mmap = mmap(NULL, u->out_mmap_length, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ if (mode == O_RDWR) {
+ pa_log(__FILE__": mmap filed for output. Changing to O_RDONLY mode.\n");
+ mode = O_RDONLY;
+ } else {
+ pa_log(__FILE__": mmap(): %s\n", strerror(errno));
+ goto fail;
+ }
+ } else {
+ pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec);
+
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &u->sample_spec, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
+
+ u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments);
+
+ enable_bits |= PCM_ENABLE_OUTPUT;
+ }
+ }
+
+ zero = 0;
+ if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ assert(u->source || u->sink);
+
+ u->io_event = c->mainloop->io_new(c->mainloop, u->fd, (u->source ? PA_IO_EVENT_INPUT : 0) | (u->sink ? PA_IO_EVENT_OUTPUT : 0), io_callback, u);
+ assert(u->io_event);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->out_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->out_fragments; i++)
+ if (u->out_memblocks[i])
+ pa_memblock_unref_fixed(u->out_memblocks[i]);
+ pa_xfree(u->out_memblocks);
+ }
+
+ if (u->in_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->in_fragments; i++)
+ if (u->in_memblocks[i])
+ pa_memblock_unref_fixed(u->in_memblocks[i]);
+ pa_xfree(u->in_memblocks);
+ }
+
+ if (u->in_mmap && u->in_mmap != MAP_FAILED)
+ munmap(u->in_mmap, u->in_mmap_length);
+
+ if (u->out_mmap && u->out_mmap != MAP_FAILED)
+ munmap(u->out_mmap, u->out_mmap_length);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->io_event)
+ u->core->mainloop->io_free(u->io_event);
+
+ if (u->fd >= 0)
+ close(u->fd);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c
new file mode 100644
index 00000000..04458419
--- /dev/null
+++ b/src/modules/module-oss.c
@@ -0,0 +1,468 @@
+/* $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 <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/oss-util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-oss-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("OSS Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_iochannel *io;
+ pa_core *core;
+
+ pa_memchunk memchunk, silence;
+
+ uint32_t in_fragment_size, out_fragment_size, sample_size;
+ int use_getospace, use_getispace;
+
+ int fd;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "oss_output"
+#define DEFAULT_SOURCE_NAME "oss_input"
+#define DEFAULT_DEVICE "/dev/dsp"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u) {
+ pa_memchunk *memchunk;
+ ssize_t r;
+ size_t l;
+ int loop = 0;
+
+ assert(u);
+
+ if (!u->sink || !pa_iochannel_is_writable(u->io))
+ return;
+
+ update_usage(u);
+
+ l = u->out_fragment_size;
+
+ if (u->use_getospace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
+ u->use_getospace = 0;
+ else {
+ if (info.bytes/l > 0) {
+ l = (info.bytes/l)*l;
+ loop = 1;
+ }
+ }
+ }
+
+ do {
+ memchunk = &u->memchunk;
+
+ if (!memchunk->length)
+ if (pa_sink_render(u->sink, l, memchunk) < 0)
+ memchunk = &u->silence;
+
+ assert(memchunk->memblock);
+ assert(memchunk->memblock->data);
+ assert(memchunk->length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ break;
+ }
+
+ if (memchunk == &u->silence)
+ assert(r % u->sample_size == 0);
+ else {
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ l = l > (size_t) r ? l - r : 0;
+ } while (loop && l > 0);
+}
+
+static void do_read(struct userdata *u) {
+ pa_memchunk memchunk;
+ ssize_t r;
+ size_t l;
+ int loop = 0;
+ assert(u);
+
+ if (!u->source || !pa_iochannel_is_readable(u->io) || !pa_idxset_size(u->source->outputs))
+ return;
+
+ update_usage(u);
+
+ l = u->in_fragment_size;
+
+ if (u->use_getispace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0)
+ u->use_getispace = 0;
+ else {
+ if (info.bytes/l > 0) {
+ l = (info.bytes/l)*l;
+ loop = 1;
+ }
+ }
+ }
+
+ do {
+ memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
+ assert(memchunk.memblock);
+ if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ if (errno != EAGAIN)
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ break;
+ }
+
+ assert(r <= (ssize_t) memchunk.memblock->length);
+ memchunk.length = memchunk.memblock->length = r;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ l = l > (size_t) r ? l - r : 0;
+ } while (loop && l > 0);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+ do_read(u);
+}
+
+static void source_notify_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+ do_read(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ int arg;
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) {
+ pa_log_info(__FILE__": device doesn't support SNDCTL_DSP_GETODELAY: %s\n", strerror(errno));
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ r += pa_bytes_to_usec(arg, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ audio_buf_info info;
+ assert(s && u && u->source);
+
+ if (!u->use_getispace)
+ return 0;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ u->use_getispace = 0;
+ return 0;
+ }
+
+ if (info.bytes <= 0)
+ return 0;
+
+ return pa_bytes_to_usec(info.bytes, &s->sample_spec);
+}
+
+static int sink_get_hw_volume(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+
+ if (ioctl(u->fd, SOUND_MIXER_READ_PCM, &vol) < 0) {
+ pa_log_info(__FILE__": device doesn't support reading mixer settings: %s\n", strerror(errno));
+ s->get_hw_volume = NULL;
+ return -1;
+ }
+
+ s->hw_volume.values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ if ((s->hw_volume.channels = s->sample_spec.channels) >= 2)
+ s->hw_volume.values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ pa_log_info(__FILE__": Read mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
+ return 0;
+}
+
+static int sink_set_hw_volume(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+
+ vol = (s->hw_volume.values[0]*100)/PA_VOLUME_NORM;
+
+ if (s->sample_spec.channels >= 2)
+ vol |= ((s->hw_volume.values[1]*100)/PA_VOLUME_NORM) << 8;
+
+ if (ioctl(u->fd, SOUND_MIXER_WRITE_PCM, &vol) < 0) {
+ pa_log_info(__FILE__": device doesn't support writing mixer settings: %s\n", strerror(errno));
+ s->set_hw_volume = NULL;
+ return -1;
+ }
+
+ pa_log_info(__FILE__": Wrote mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct audio_buf_info info;
+ struct userdata *u = NULL;
+ const char *p;
+ int fd = -1;
+ int nfrags, frag_size, in_frag_size, out_frag_size;
+ int mode;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ nfrags = 12;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, NULL)) < 0)
+ goto fail;
+
+ pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (nfrags >= 2 && frag_size >= 1)
+ if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0)
+ goto fail;
+
+ if (pa_oss_auto_format(fd, &ss) < 0)
+ goto fail;
+
+ if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETBLKSIZE: %s\n", strerror(errno));
+ goto fail;
+ }
+ assert(frag_size);
+ in_frag_size = out_frag_size = frag_size;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->use_getospace = u->use_getispace = 0;
+
+ if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
+ pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ in_frag_size = info.fragsize;
+ u->use_getispace = 1;
+ }
+
+ if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
+ pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ out_frag_size = info.fragsize;
+ u->use_getospace = 1;
+ }
+
+ if (mode != O_WRONLY) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->notify = source_notify_cb;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
+ } else
+ u->source = NULL;
+
+ if (mode != O_RDONLY) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->get_hw_volume = sink_get_hw_volume;
+ u->sink->set_hw_volume = sink_set_hw_volume;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : -1);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+ u->fd = fd;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+ u->sample_size = pa_frame_size(&ss);
+
+ u->out_fragment_size = out_frag_size;
+ u->in_fragment_size = in_frag_size;
+ u->silence.memblock = pa_memblock_new(u->silence.length = u->out_fragment_size, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ /*
+ * Some crappy drivers do not start the recording until we read something.
+ * Without this snippet, poll will never register the fd as ready.
+ */
+ if (u->source) {
+ char buf[u->sample_size];
+ read(u->fd, buf, u->sample_size);
+ }
+
+ /* Read mixer settings */
+ if (u->sink)
+ sink_get_hw_volume(u->sink);
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ pa_iochannel_free(u->io);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
new file mode 100644
index 00000000..6ace377f
--- /dev/null
+++ b/src/modules/module-pipe-sink.c
@@ -0,0 +1,237 @@
+/* $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 <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-pipe-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("UNIX pipe sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_FIFO_NAME "/tmp/music.output"
+#define DEFAULT_SINK_NAME "fifo_output"
+
+struct userdata {
+ pa_core *core;
+
+ char *filename;
+
+ pa_sink *sink;
+ pa_iochannel *io;
+ pa_defer_event *defer_event;
+
+ pa_memchunk memchunk;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void do_write(struct userdata *u) {
+ ssize_t r;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer_event, 0);
+
+ if (!pa_iochannel_is_writable(u->io))
+ return;
+
+ pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
+
+ if (!u->memchunk.length)
+ if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0)
+ return;
+
+ assert(u->memchunk.memblock && u->memchunk.length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+}
+
+static void notify_cb(pa_sink*s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ if (pa_iochannel_is_writable(u->io))
+ u->core->mainloop->defer_enable(u->defer_event, 1);
+}
+
+static pa_usec_t get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ return u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0;
+}
+
+static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ struct stat st;
+ const char *p;
+ int fd = -1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
+
+ if ((fd = open(p, O_RDWR)) < 0) {
+ pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ pa_fd_set_cloexec(fd, 1);
+
+ if (fstat(fd, &st) < 0) {
+ pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log(__FILE__": '%s' is not a FIFO.\n", p);
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->filename = pa_xstrdup(p);
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create sink.\n");
+ goto fail;
+ }
+ u->sink->notify = notify_cb;
+ u->sink->get_latency = get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Unix FIFO sink '%s'", p);
+ assert(u->sink->description);
+
+ u->io = pa_iochannel_new(c->mainloop, -1, fd);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+
+ u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
+ assert(u->defer_event);
+ c->mainloop->defer_enable(u->defer_event, 0);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ close(fd);
+
+ pa__done(c, m);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ pa_iochannel_free(u->io);
+ u->core->mainloop->defer_free(u->defer_event);
+
+ assert(u->filename);
+ unlink(u->filename);
+ pa_xfree(u->filename);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
new file mode 100644
index 00000000..a7bb0ce7
--- /dev/null
+++ b/src/modules/module-pipe-source.c
@@ -0,0 +1,210 @@
+/* $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 <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-pipe-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("UNIX pipe source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("source_name=<name for the source> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_FIFO_NAME "/tmp/music.input"
+#define DEFAULT_SOURCE_NAME "fifo_input"
+
+struct userdata {
+ pa_core *core;
+
+ char *filename;
+
+ pa_source *source;
+ pa_iochannel *io;
+ pa_module *module;
+ pa_memchunk chunk;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "channels",
+ "format",
+ "source_name",
+ NULL
+};
+
+static void do_read(struct userdata *u) {
+ ssize_t r;
+ pa_memchunk chunk;
+ assert(u);
+
+ if (!pa_iochannel_is_readable(u->io))
+ return;
+
+ pa_module_set_used(u->module, pa_idxset_size(u->source->outputs));
+
+ if (!u->chunk.memblock) {
+ u->chunk.memblock = pa_memblock_new(1024, u->core->memblock_stat);
+ u->chunk.index = chunk.length = 0;
+ }
+
+ assert(u->chunk.memblock && u->chunk.memblock->length > u->chunk.index);
+ if ((r = pa_iochannel_read(u->io, (uint8_t*) u->chunk.memblock->data + u->chunk.index, u->chunk.memblock->length - u->chunk.index)) <= 0) {
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ u->chunk.length = r;
+ pa_source_post(u->source, &u->chunk);
+ u->chunk.index += r;
+
+ if (u->chunk.index >= u->chunk.memblock->length) {
+ u->chunk.index = u->chunk.length = 0;
+ pa_memblock_unref(u->chunk.memblock);
+ u->chunk.memblock = NULL;
+ }
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_read(u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ struct stat st;
+ const char *p;
+ int fd = -1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
+
+ if ((fd = open(p, O_RDWR)) < 0) {
+ pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ pa_fd_set_cloexec(fd, 1);
+
+ if (fstat(fd, &st) < 0) {
+ pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log(__FILE__": '%s' is not a FIFO.\n", p);
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+
+ u->filename = pa_xstrdup(p);
+ u->core = c;
+
+ if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create source.\n");
+ goto fail;
+ }
+ u->source->userdata = u;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Unix FIFO source '%s'", p);
+ assert(u->source->description);
+
+ u->io = pa_iochannel_new(c->mainloop, fd, -1);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+
+ u->chunk.memblock = NULL;
+ u->chunk.index = u->chunk.length = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ close(fd);
+
+ pa__done(c, m);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->chunk.memblock)
+ pa_memblock_unref(u->chunk.memblock);
+
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ pa_iochannel_free(u->io);
+
+ assert(u->filename);
+ unlink(u->filename);
+ pa_xfree(u->filename);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c
new file mode 100644
index 00000000..469ddcee
--- /dev/null
+++ b/src/modules/module-protocol-stub.c
@@ -0,0 +1,261 @@
+/* $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 <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <polypcore/winsock.h>
+
+#include <polypcore/module.h>
+#include <polypcore/socket-server.h>
+#include <polypcore/socket-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/native-common.h>
+
+#ifdef USE_TCP_SOCKETS
+#define SOCKET_DESCRIPTION "(TCP sockets)"
+#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
+#elif defined(USE_TCP6_SOCKETS)
+#define SOCKET_DESCRIPTION "(TCP/IPv6 sockets)"
+#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
+#else
+#define SOCKET_DESCRIPTION "(UNIX sockets)"
+#define SOCKET_USAGE "socket=<path to UNIX socket>"
+#endif
+
+#if defined(USE_PROTOCOL_SIMPLE)
+ #include <polypcore/protocol-simple.h>
+ #define protocol_new pa_protocol_simple_new
+ #define protocol_free pa_protocol_simple_free
+ #define TCPWRAP_SERVICE "polypaudio-simple"
+ #define IPV4_PORT 4711
+ #define UNIX_SOCKET "simple"
+ #define MODULE_ARGUMENTS "rate", "format", "channels", "sink", "source", "playback", "record",
+ #if defined(USE_TCP_SOCKETS)
+ #include "module-simple-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-simple-protocol-tcp6-symdef.h"
+ #else
+ #include "module-simple-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("rate=<sample rate> format=<sample format> channels=<number of channels> sink=<sink to connect to> source=<source to connect to> playback=<enable playback?> record=<enable record?> "SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_CLI)
+ #include <polypcore/protocol-cli.h>
+ #define protocol_new pa_protocol_cli_new
+ #define protocol_free pa_protocol_cli_free
+ #define TCPWRAP_SERVICE "polypaudio-cli"
+ #define IPV4_PORT 4712
+ #define UNIX_SOCKET "cli"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-cli-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-cli-protocol-tcp6-symdef.h"
+ #else
+ #include "module-cli-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE(SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_HTTP)
+ #include <polypcore/protocol-http.h>
+ #define protocol_new pa_protocol_http_new
+ #define protocol_free pa_protocol_http_free
+ #define TCPWRAP_SERVICE "polypaudio-http"
+ #define IPV4_PORT 4714
+ #define UNIX_SOCKET "http"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-http-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-http-protocol-tcp6-symdef.h"
+ #else
+ #include "module-http-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE(SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_NATIVE)
+ #include <polypcore/protocol-native.h>
+ #define protocol_new pa_protocol_native_new
+ #define protocol_free pa_protocol_native_free
+ #define TCPWRAP_SERVICE "polypaudio-native"
+ #define IPV4_PORT PA_NATIVE_DEFAULT_PORT
+ #define UNIX_SOCKET PA_NATIVE_DEFAULT_UNIX_SOCKET
+ #define MODULE_ARGUMENTS "public", "cookie",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-native-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-native-protocol-tcp6-symdef.h"
+ #else
+ #include "module-native-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_ESOUND)
+ #include <polypcore/protocol-esound.h>
+ #include <polypcore/esound.h>
+ #define protocol_new pa_protocol_esound_new
+ #define protocol_free pa_protocol_esound_free
+ #define TCPWRAP_SERVICE "esound"
+ #define IPV4_PORT ESD_DEFAULT_PORT
+ #define UNIX_SOCKET ESD_UNIX_SOCKET_NAME
+ #define MODULE_ARGUMENTS "sink", "source", "public", "cookie",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-esound-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-esound-protocol-tcp6-symdef.h"
+ #else
+ #include "module-esound-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("sink=<sink to connect to> source=<source to connect to> public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
+#else
+ #error "Broken build system"
+#endif
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ MODULE_ARGUMENTS
+#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
+ "port",
+ "loopback",
+#else
+ "socket",
+#endif
+ NULL
+};
+
+static pa_socket_server *create_socket_server(pa_core *c, pa_modargs *ma) {
+ pa_socket_server *s;
+#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
+ int loopback = 1;
+ uint32_t port = IPV4_PORT;
+
+ if (pa_modargs_get_value_boolean(ma, "loopback", &loopback) < 0) {
+ pa_log(__FILE__": loopback= expects a boolean argument.\n");
+ return NULL;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) {
+ pa_log(__FILE__": port= expects a numerical argument between 1 and 65535.\n");
+ return NULL;
+ }
+
+#ifdef USE_TCP6_SOCKETS
+ if (!(s = pa_socket_server_new_ipv6(c->mainloop, loopback ? (const uint8_t*) &in6addr_loopback : (const uint8_t*) &in6addr_any, port)))
+ return NULL;
+#else
+ if (!(s = pa_socket_server_new_ipv4(c->mainloop, loopback ? INADDR_LOOPBACK : INADDR_ANY, port, TCPWRAP_SERVICE)))
+ return NULL;
+#endif
+
+#else
+ int r;
+ const char *v;
+ char tmp[PATH_MAX];
+
+ v = pa_modargs_get_value(ma, "socket", UNIX_SOCKET);
+ assert(v);
+
+ pa_runtime_path(v, tmp, sizeof(tmp));
+
+ if (pa_make_secure_parent_dir(tmp) < 0) {
+ pa_log(__FILE__": Failed to create secure socket directory.\n");
+ return NULL;
+ }
+
+ if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
+ pa_log(__FILE__": Failed to remove stale UNIX socket '%s': %s\n", tmp, strerror(errno));
+ return NULL;
+ }
+
+ if (r)
+ pa_log(__FILE__": Removed stale UNIX socket '%s'.", tmp);
+
+ if (!(s = pa_socket_server_new_unix(c->mainloop, tmp)))
+ return NULL;
+
+#endif
+ return s;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_socket_server *s;
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (!(s = create_socket_server(c, ma)))
+ goto finish;
+
+ if (!(m->userdata = protocol_new(c, s, m, ma))) {
+ pa_socket_server_unref(s);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+#if defined(USE_PROTOCOL_ESOUND)
+ if (remove (ESD_UNIX_SOCKET_NAME) != 0)
+ pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_NAME, strerror (errno));
+ if (remove (ESD_UNIX_SOCKET_DIR) != 0)
+ pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_DIR, strerror (errno));
+#endif
+
+ protocol_free(m->userdata);
+}
diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c
new file mode 100644
index 00000000..446e3974
--- /dev/null
+++ b/src/modules/module-sine.c
@@ -0,0 +1,182 @@
+/* $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 <polypcore/sink-input.h>
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+
+#include "module-sine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Sine wave generator")
+PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink_input *sink_input;
+ pa_memblock *memblock;
+ size_t peek_index;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "frequency",
+ NULL,
+};
+
+static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) {
+ struct userdata *u;
+ assert(i && chunk && i->userdata);
+ u = i->userdata;
+
+ chunk->memblock = pa_memblock_ref(u->memblock);
+ chunk->index = u->peek_index;
+ chunk->length = u->memblock->length - u->peek_index;
+ return 0;
+}
+
+static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
+ struct userdata *u;
+ assert(i && chunk && length && i->userdata);
+ u = i->userdata;
+
+ assert(chunk->memblock == u->memblock && length <= u->memblock->length-u->peek_index);
+
+ u->peek_index += length;
+
+ if (u->peek_index >= u->memblock->length)
+ u->peek_index = 0;
+}
+
+static void sink_input_kill(pa_sink_input *i) {
+ struct userdata *u;
+ assert(i && i->userdata);
+ u = i->userdata;
+
+ pa_sink_input_disconnect(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+static void calc_sine(float *f, size_t l, float freq) {
+ size_t i;
+
+ l /= sizeof(float);
+
+ for (i = 0; i < l; i++)
+ f[i] = (float) sin((double) i/l*M_PI*2*freq)/2;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ pa_sink *sink;
+ const char *sink_name;
+ pa_sample_spec ss;
+ uint32_t frequency;
+ char t[256];
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ u->sink_input = NULL;
+ u->memblock = NULL;
+
+ sink_name = pa_modargs_get_value(ma, "sink", NULL);
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": No such sink.\n");
+ goto fail;
+ }
+
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = sink->sample_spec.rate;
+ ss.channels = 1;
+
+ frequency = 440;
+ if (pa_modargs_get_value_u32(ma, "frequency", &frequency) < 0 || frequency < 1 || frequency > ss.rate/2) {
+ pa_log(__FILE__": Invalid frequency specification\n");
+ goto fail;
+ }
+
+ u->memblock = pa_memblock_new(pa_bytes_per_second(&ss), c->memblock_stat);
+ calc_sine(u->memblock->data, u->memblock->length, frequency);
+
+ snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
+ if (!(u->sink_input = pa_sink_input_new(sink, __FILE__, t, &ss, NULL, 0, -1)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek;
+ u->sink_input->drop = sink_input_drop;
+ u->sink_input->kill = sink_input_kill;
+ u->sink_input->userdata = u;
+ u->sink_input->owner = m;
+
+ u->peek_index = 0;
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u = m->userdata;
+ assert(c && m);
+
+ if (!u)
+ return;
+
+ if (u->sink_input) {
+ pa_sink_input_disconnect(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ }
+
+ if (u->memblock)
+ pa_memblock_unref(u->memblock);
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
new file mode 100644
index 00000000..e0745fc0
--- /dev/null
+++ b/src/modules/module-solaris.c
@@ -0,0 +1,488 @@
+/* $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 <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <signal.h>
+#include <stropts.h>
+#include <sys/conf.h>
+#include <sys/audio.h>
+
+#include <polyp/mainloop-signal.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-solaris-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Solaris Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> buffer_size=<record buffer size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_iochannel *io;
+ pa_core *core;
+ pa_signal_event *sig;
+
+ pa_memchunk memchunk, silence;
+
+ uint32_t sample_size;
+ uint32_t buffer_size;
+ unsigned int written_bytes, read_bytes;
+
+ int fd;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "buffer_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "solaris_output"
+#define DEFAULT_SOURCE_NAME "solaris_input"
+#define DEFAULT_DEVICE "/dev/audio"
+
+#define CHUNK_SIZE 2048
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u) {
+ audio_info_t info;
+ int err;
+ pa_memchunk *memchunk;
+ size_t len;
+ ssize_t r;
+
+ assert(u);
+
+ if (!u->sink || !pa_iochannel_is_writable(u->io))
+ return;
+
+ update_usage(u);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ /*
+ * Since we cannot modify the size of the output buffer we fake it
+ * by not filling it more than u->buffer_size.
+ */
+ len = u->buffer_size;
+ len -= u->written_bytes - (info.play.samples * u->sample_size);
+
+ /*
+ * Do not fill more than half the buffer in one chunk since we only
+ * get notifications upon completion of entire chunks.
+ */
+ if (len > (u->buffer_size / 2))
+ len = u->buffer_size / 2;
+
+ if (len < u->sample_size)
+ return;
+
+ memchunk = &u->memchunk;
+
+ if (!memchunk->length)
+ if (pa_sink_render(u->sink, len, memchunk) < 0)
+ memchunk = &u->silence;
+
+ assert(memchunk->memblock);
+ assert(memchunk->memblock->data);
+ assert(memchunk->length);
+
+ if (memchunk->length < len)
+ len = memchunk->length;
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, len)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ if (memchunk == &u->silence)
+ assert(r % u->sample_size == 0);
+ else {
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ u->written_bytes += r;
+
+ /*
+ * Write 0 bytes which will generate a SIGPOLL when "played".
+ */
+ if (write(u->fd, NULL, 0) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+}
+
+static void do_read(struct userdata *u) {
+ pa_memchunk memchunk;
+ int err, l;
+ ssize_t r;
+ assert(u);
+
+ if (!u->source || !pa_iochannel_is_readable(u->io))
+ return;
+
+ update_usage(u);
+
+ err = ioctl(u->fd, I_NREAD, &l);
+ assert(err >= 0);
+
+ memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
+ assert(memchunk.memblock);
+ if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ if (errno != EAGAIN)
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ assert(r <= (ssize_t) memchunk.memblock->length);
+ memchunk.length = memchunk.memblock->length = r;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ u->read_bytes += r;
+}
+
+static void io_callback(pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+ do_read(u);
+}
+
+void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ audio_info_t info;
+ int err;
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec);
+ r -= pa_bytes_to_usec(info.play.samples * u->sample_size, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ audio_info_t info;
+ int err;
+ assert(s && u && u->source);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ r += pa_bytes_to_usec(info.record.samples * u->sample_size, &s->sample_spec);
+ r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
+
+ return r;
+}
+
+static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ if (mode != O_RDONLY) {
+ info.play.sample_rate = ss->rate;
+ info.play.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (mode != O_WRONLY) {
+ info.record.sample_rate = ss->rate;
+ info.record.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.record.precision = 16;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log(__FILE__": AUDIO_SETINFO: Unsupported sample format.\n");
+ else
+ pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_solaris_set_buffer(int fd, int buffer_size) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ info.record.buffer_size = buffer_size;
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log(__FILE__": AUDIO_SETINFO: Unsupported buffer size.\n");
+ else
+ pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ const char *p;
+ int fd = -1;
+ int buffer_size;
+ int mode;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ buffer_size = 16384;
+ if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) {
+ pa_log(__FILE__": failed to parse buffer size argument\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0)
+ goto fail;
+
+ pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (pa_solaris_auto_format(fd, mode, &ss) < 0)
+ goto fail;
+
+ if ((mode != O_WRONLY) && (buffer_size >= 1))
+ if (pa_solaris_set_buffer(fd, buffer_size) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+
+ if (mode != O_WRONLY) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
+ } else
+ u->source = NULL;
+
+ if (mode != O_RDONLY) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+ u->fd = fd;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+ u->sample_size = pa_frame_size(&ss);
+ u->buffer_size = buffer_size;
+
+ u->silence.memblock = pa_memblock_new(u->silence.length = CHUNK_SIZE, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->written_bytes = 0;
+ u->read_bytes = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
+ assert(u->sig);
+ ioctl(u->fd, I_SETSIG, S_MSG);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ ioctl(u->fd, I_SETSIG, 0);
+ pa_signal_free(u->sig);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ pa_iochannel_free(u->io);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
new file mode 100644
index 00000000..5ee10fda
--- /dev/null
+++ b/src/modules/module-tunnel.c
@@ -0,0 +1,688 @@
+/* $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 <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/sink-input.h>
+#include <polypcore/pdispatch.h>
+#include <polypcore/pstream.h>
+#include <polypcore/pstream-util.h>
+#include <polypcore/authkey.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/socket-util.h>
+#include <polypcore/authkey-prop.h>
+
+#ifdef TUNNEL_SINK
+#include "module-tunnel-sink-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sinks")
+PA_MODULE_USAGE("server=<address> sink=<remote sink name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name for the local sink>")
+#else
+#include "module-tunnel-source-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sources")
+PA_MODULE_USAGE("server=<address> source=<remote source name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> source_name=<name for the local source>")
+#endif
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+#define DEFAULT_SINK_NAME "tunnel"
+#define DEFAULT_SOURCE_NAME "tunnel"
+
+#define DEFAULT_TLENGTH (44100*2*2/10) //(10240*8)
+#define DEFAULT_MAXLENGTH ((DEFAULT_TLENGTH*3)/2)
+#define DEFAULT_MINREQ 512
+#define DEFAULT_PREBUF (DEFAULT_TLENGTH-DEFAULT_MINREQ)
+#define DEFAULT_FRAGSIZE 1024
+
+#define DEFAULT_TIMEOUT 5
+
+#define LATENCY_INTERVAL 10
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "format",
+ "channels",
+ "rate",
+#ifdef TUNNEL_SINK
+ "sink_name",
+ "sink",
+#else
+ "source_name",
+ "source",
+#endif
+ NULL,
+};
+
+static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+#ifdef TUNNEL_SINK
+static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+#endif
+
+static const pa_pdispatch_callback command_table[PA_COMMAND_MAX] = {
+#ifdef TUNNEL_SINK
+ [PA_COMMAND_REQUEST] = command_request,
+#endif
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed,
+ [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed
+};
+
+struct userdata {
+ pa_socket_client *client;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+
+ char *server_name;
+#ifdef TUNNEL_SINK
+ char *sink_name;
+ pa_sink *sink;
+ uint32_t requested_bytes;
+#else
+ char *source_name;
+ pa_source *source;
+#endif
+
+ pa_module *module;
+ pa_core *core;
+
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ uint32_t ctag;
+ uint32_t device_index;
+ uint32_t channel;
+
+ pa_usec_t host_latency;
+
+ pa_time_event *time_event;
+
+ int auth_cookie_in_property;
+};
+
+static void close_stuff(struct userdata *u) {
+ assert(u);
+
+ if (u->pstream) {
+ pa_pstream_close(u->pstream);
+ pa_pstream_unref(u->pstream);
+ u->pstream = NULL;
+ }
+
+ if (u->pdispatch) {
+ pa_pdispatch_unref(u->pdispatch);
+ u->pdispatch = NULL;
+ }
+
+ if (u->client) {
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+ }
+
+#ifdef TUNNEL_SINK
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+#else
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ u->source = NULL;
+ }
+#endif
+
+ if (u->time_event) {
+ u->core->mainloop->time_free(u->time_event);
+ u->time_event = NULL;
+ }
+}
+
+static void die(struct userdata *u) {
+ assert(u);
+ close_stuff(u);
+ pa_module_unload_request(u->module);
+}
+
+static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ assert(pd && t && u && u->pdispatch == pd);
+
+ pa_log(__FILE__": stream killed\n");
+ die(u);
+}
+
+#ifdef TUNNEL_SINK
+static void send_prebuf_request(struct userdata *u) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_PREBUF_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+ pa_pstream_send_tagstruct(u->pstream, t);
+}
+
+static void send_bytes(struct userdata *u) {
+ assert(u);
+
+ if (!u->pstream)
+ return;
+
+ while (u->requested_bytes > 0) {
+ pa_memchunk chunk;
+ if (pa_sink_render(u->sink, u->requested_bytes, &chunk) < 0) {
+
+
+ if (u->requested_bytes >= DEFAULT_TLENGTH-DEFAULT_PREBUF)
+ send_prebuf_request(u);
+
+ return;
+ }
+
+ pa_pstream_send_memblock(u->pstream, u->channel, 0, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ if (chunk.length > u->requested_bytes)
+ u->requested_bytes = 0;
+ else
+ u->requested_bytes -= chunk.length;
+ }
+}
+
+static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t bytes, channel;
+ assert(pd && command == PA_COMMAND_REQUEST && t && u && u->pdispatch == pd);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &bytes) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid protocol reply\n");
+ die(u);
+ return;
+ }
+
+ if (channel != u->channel) {
+ pa_log(__FILE__": recieved data for invalid channel\n");
+ die(u);
+ return;
+ }
+
+ u->requested_bytes += bytes;
+ send_bytes(u);
+}
+
+#endif
+
+static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_usec_t buffer_usec, sink_usec, source_usec, transport_usec;
+ int playing;
+ uint32_t queue_length;
+ uint64_t counter;
+ struct timeval local, remote, now;
+ assert(pd && u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to get latency.\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+
+ if (pa_tagstruct_get_usec(t, &buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &source_usec) < 0 ||
+ pa_tagstruct_get_boolean(t, &playing) < 0 ||
+ pa_tagstruct_getu32(t, &queue_length) < 0 ||
+ pa_tagstruct_get_timeval(t, &local) < 0 ||
+ pa_tagstruct_get_timeval(t, &remote) < 0 ||
+ pa_tagstruct_getu64(t, &counter) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid reply.\n");
+ die(u);
+ return;
+ }
+
+ pa_gettimeofday(&now);
+
+ if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) {
+ /* local and remote seem to have synchronized clocks */
+#ifdef TUNNEL_SINK
+ transport_usec = pa_timeval_diff(&remote, &local);
+#else
+ transport_usec = pa_timeval_diff(&now, &remote);
+#endif
+ } else
+ transport_usec = pa_timeval_diff(&now, &local)/2;
+
+#ifdef TUNNEL_SINK
+ u->host_latency = sink_usec + transport_usec;
+#else
+ u->host_latency = source_usec + transport_usec;
+ if (u->host_latency > sink_usec)
+ u->host_latency -= sink_usec;
+ else
+ u->host_latency = 0;
+#endif
+
+/* pa_log(__FILE__": estimated host latency: %0.0f usec\n", (double) u->host_latency); */
+}
+
+static void request_latency(struct userdata *u) {
+ pa_tagstruct *t;
+ struct timeval now;
+ uint32_t tag;
+ assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY);
+#endif
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+
+ pa_gettimeofday(&now);
+ pa_tagstruct_put_timeval(t, &now);
+ pa_tagstruct_putu64(t, 0);
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u);
+}
+
+static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ assert(pd && u && u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to create stream.\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+
+ if (pa_tagstruct_getu32(t, &u->channel) < 0 ||
+ pa_tagstruct_getu32(t, &u->device_index) < 0 ||
+#ifdef TUNNEL_SINK
+ pa_tagstruct_getu32(t, &u->requested_bytes) < 0 ||
+#endif
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid reply.\n");
+ die(u);
+ return;
+ }
+
+ request_latency(u);
+#ifdef TUNNEL_SINK
+ send_bytes(u);
+#endif
+}
+
+static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *reply;
+ char name[256], un[128], hn[128];
+ assert(pd && u && u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY || !pa_tagstruct_eof(t)) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to authenticate\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+#ifdef TUNNEL_SINK
+ snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', sink '%s'",
+ pa_get_host_name(hn, sizeof(hn)),
+ pa_get_user_name(un, sizeof(un)),
+ u->sink->name);
+#else
+ snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', source '%s'",
+ pa_get_host_name(hn, sizeof(hn)),
+ pa_get_user_name(un, sizeof(un)),
+ u->source->name);
+#endif
+
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ /* We ignore the server's reply here */
+
+ reply = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->sink_name);
+ pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
+ pa_tagstruct_put_boolean(reply, 0);
+ pa_tagstruct_putu32(reply, DEFAULT_TLENGTH);
+ pa_tagstruct_putu32(reply, DEFAULT_PREBUF);
+ pa_tagstruct_putu32(reply, DEFAULT_MINREQ);
+ pa_tagstruct_putu32(reply, PA_VOLUME_NORM);
+#else
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->source_name);
+ pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
+ pa_tagstruct_put_boolean(reply, 0);
+ pa_tagstruct_putu32(reply, DEFAULT_FRAGSIZE);
+#endif
+
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u);
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && u);
+
+ pa_log(__FILE__": stream died.\n");
+ die(u);
+}
+
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && packet && u);
+
+ if (pa_pdispatch_run(u->pdispatch, packet, u) < 0) {
+ pa_log(__FILE__": invalid packet\n");
+ die(u);
+ }
+}
+
+#ifndef TUNNEL_SINK
+static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, uint32_t delta, const pa_memchunk *chunk, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && chunk && u);
+
+ if (channel != u->channel) {
+ pa_log(__FILE__": recieved memory block on bad channel.\n");
+ die(u);
+ return;
+ }
+
+ pa_source_post(u->source, chunk);
+}
+#endif
+
+static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(sc && u && u->client == sc);
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log(__FILE__": connection failed.\n");
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->memblock_stat);
+ u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX);
+
+ pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u);
+ pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u);
+#ifndef TUNNEL_SINK
+ pa_pstream_set_recieve_memblock_callback(u->pstream, pstream_memblock_callback, u);
+#endif
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_AUTH);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie));
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u);
+
+}
+
+#ifdef TUNNEL_SINK
+static void sink_notify(pa_sink*sink) {
+ struct userdata *u;
+ assert(sink && sink->userdata);
+ u = sink->userdata;
+
+ send_bytes(u);
+}
+
+static pa_usec_t sink_get_latency(pa_sink *sink) {
+ struct userdata *u;
+ uint32_t l;
+ pa_usec_t usec = 0;
+ assert(sink && sink->userdata);
+ u = sink->userdata;
+
+ l = DEFAULT_TLENGTH;
+
+ if (l > u->requested_bytes) {
+ l -= u->requested_bytes;
+ usec += pa_bytes_to_usec(l, &u->sink->sample_spec);
+ }
+
+ usec += u->host_latency;
+
+ return usec;
+}
+#else
+static pa_usec_t source_get_latency(pa_source *source) {
+ struct userdata *u;
+ assert(source && source->userdata);
+ u = source->userdata;
+
+ return u->host_latency;
+}
+#endif
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+ assert(m && e && u);
+
+ request_latency(u);
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ m->time_restart(e, &ntv);
+}
+
+static int load_key(struct userdata *u, const char*fn) {
+ assert(u);
+
+ u->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug(__FILE__": using already loaded auth cookie.\n");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug(__FILE__": loading cookie from disk.\n");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ struct timeval ntv;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+ u->core = c;
+ u->client = NULL;
+ u->pdispatch = NULL;
+ u->pstream = NULL;
+ u->server_name = NULL;
+#ifdef TUNNEL_SINK
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));;
+ u->sink = NULL;
+ u->requested_bytes = 0;
+#else
+ u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;
+ u->source = NULL;
+#endif
+ u->ctag = 1;
+ u->device_index = u->channel = PA_INVALID_INDEX;
+ u->host_latency = 0;
+ u->auth_cookie_in_property = 0;
+ u->time_event = NULL;
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) {
+ pa_log(__FILE__": no server specified.\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ if (!(u->client = pa_socket_client_new_string(c->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
+ pa_log(__FILE__": failed to connect to server '%s'\n", u->server_name);
+ goto fail;
+ }
+
+ if (!u->client)
+ goto fail;
+
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+#ifdef TUNNEL_SINK
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create sink.\n");
+ goto fail;
+ }
+
+ u->sink->notify = sink_notify;
+ u->sink->get_latency = sink_get_latency;
+ u->sink->userdata = u;
+ u->sink->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->sink_name ? u->sink_name : "", u->sink_name ? "@" : "", u->server_name);
+
+ pa_sink_set_owner(u->sink, m);
+#else
+ if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create source.\n");
+ goto fail;
+ }
+
+ u->source->get_latency = source_get_latency;
+ u->source->userdata = u;
+ u->source->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->source_name ? u->source_name : "", u->source_name ? "@" : "", u->server_name);
+
+ pa_source_set_owner(u->source, m);
+#endif
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ u->time_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata* u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ close_stuff(u);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+#ifdef TUNNEL_SINK
+ pa_xfree(u->sink_name);
+#else
+ pa_xfree(u->source_name);
+#endif
+ pa_xfree(u->server_name);
+
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c
new file mode 100644
index 00000000..8809c31d
--- /dev/null
+++ b/src/modules/module-waveout.c
@@ -0,0 +1,583 @@
+/* $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 <windows.h>
+#include <mmsystem.h>
+#include <assert.h>
+
+#include <polyp/mainloop-api.h>
+
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+#include <polypcore/xmalloc.h>
+
+#include "module-waveout-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+#define DEFAULT_SINK_NAME "wave_output"
+#define DEFAULT_SOURCE_NAME "wave_input"
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_core *core;
+ pa_time_event *event;
+ pa_defer_event *defer;
+ pa_usec_t poll_timeout;
+
+ uint32_t fragments, fragment_size;
+
+ uint32_t free_ofrags, free_ifrags;
+
+ DWORD written_bytes;
+
+ int cur_ohdr, cur_ihdr;
+ unsigned int oremain;
+ WAVEHDR *ohdrs, *ihdrs;
+ pa_memchunk silence;
+
+ HWAVEOUT hwo;
+ HWAVEIN hwi;
+ pa_module *module;
+
+ CRITICAL_SECTION crit;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u)
+{
+ uint32_t free_frags, remain;
+ pa_memchunk memchunk, *cur_chunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->sink)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ofrags;
+ u->free_ofrags = 0;
+
+ LeaveCriticalSection(&u->crit);
+
+ while (free_frags) {
+ hdr = &u->ohdrs[u->cur_ohdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+
+ remain = u->oremain;
+ while (remain) {
+ cur_chunk = &memchunk;
+
+ if (pa_sink_render(u->sink, remain, cur_chunk) < 0) {
+ /*
+ * Don't fill with silence unless we're getting close to
+ * underflowing.
+ */
+ if (free_frags > u->fragments/2)
+ cur_chunk = &u->silence;
+ else {
+ EnterCriticalSection(&u->crit);
+
+ u->free_ofrags += free_frags;
+
+ LeaveCriticalSection(&u->crit);
+
+ u->oremain = remain;
+ return;
+ }
+ }
+
+ assert(cur_chunk->memblock);
+ assert(cur_chunk->memblock->data);
+ assert(cur_chunk->length);
+
+ memcpy(hdr->lpData + u->fragment_size - remain,
+ (char*)cur_chunk->memblock->data + cur_chunk->index,
+ (cur_chunk->length < remain)?cur_chunk->length:remain);
+
+ remain -= (cur_chunk->length < remain)?cur_chunk->length:remain;
+
+ if (cur_chunk != &u->silence) {
+ pa_memblock_unref(cur_chunk->memblock);
+ cur_chunk->memblock = NULL;
+ }
+ }
+
+ res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d\n",
+ res);
+ }
+ res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d\n",
+ res);
+ }
+
+ u->written_bytes += u->fragment_size;
+
+ free_frags--;
+ u->cur_ohdr++;
+ u->cur_ohdr %= u->fragments;
+ u->oremain = u->fragment_size;
+ }
+}
+
+static void do_read(struct userdata *u)
+{
+ uint32_t free_frags;
+ pa_memchunk memchunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->source)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+ u->free_ifrags = 0;
+
+ LeaveCriticalSection(&u->crit);
+
+ while (free_frags) {
+ hdr = &u->ihdrs[u->cur_ihdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+
+ if (hdr->dwBytesRecorded) {
+ memchunk.memblock = pa_memblock_new(hdr->dwBytesRecorded, u->core->memblock_stat);
+ assert(memchunk.memblock);
+
+ memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded);
+
+ memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+ }
+
+ res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d\n",
+ res);
+ }
+ res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d\n",
+ res);
+ }
+
+ free_frags--;
+ u->cur_ihdr++;
+ u->cur_ihdr %= u->fragments;
+ }
+}
+
+static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+
+ assert(u);
+
+ update_usage(u);
+
+ do_write(u);
+ do_read(u);
+
+ pa_gettimeofday(&ntv);
+ pa_timeval_add(&ntv, u->poll_timeout);
+
+ a->time_restart(e, &ntv);
+}
+
+static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ struct userdata *u = userdata;
+
+ assert(u);
+
+ a->defer_enable(e, 0);
+
+ do_write(u);
+ do_read(u);
+}
+
+static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WOM_DONE)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ofrags++;
+ assert(u->free_ofrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WIM_DATA)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ifrags++;
+ assert(u->free_ifrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ MMTIME mmt;
+ assert(s && u && u->sink);
+
+ memset(&mmt, 0, sizeof(mmt));
+ mmt.wType = TIME_BYTES;
+ if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
+ return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec);
+ else {
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ofrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size,
+ &s->sample_spec);
+ }
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ assert(s && u && u->sink);
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec);
+
+ fprintf(stderr, "Latency: %d us\n", (int)r);
+
+ return r;
+}
+
+static void notify_sink_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static void notify_source_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
+ wf->wFormatTag = WAVE_FORMAT_PCM;
+
+ if (ss->channels > 2) {
+ pa_log_error(__FILE__": ERROR: More than two channels not supported.\n");
+ return -1;
+ }
+
+ wf->nChannels = ss->channels;
+
+ switch (ss->rate) {
+ case 8000:
+ case 11025:
+ case 22005:
+ case 44100:
+ break;
+ default:
+ pa_log_error(__FILE__": ERROR: Unsupported sample rate.\n");
+ return -1;
+ }
+
+ wf->nSamplesPerSec = ss->rate;
+
+ if (ss->format == PA_SAMPLE_U8)
+ wf->wBitsPerSample = 8;
+ else if (ss->format == PA_SAMPLE_S16NE)
+ wf->wBitsPerSample = 16;
+ else {
+ pa_log_error(__FILE__": ERROR: Unsupported sample format.\n");
+ return -1;
+ }
+
+ wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
+ wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
+
+ wf->cbSize = 0;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ HWAVEOUT hwo = INVALID_HANDLE_VALUE;
+ HWAVEIN hwi = INVALID_HANDLE_VALUE;
+ WAVEFORMATEX wf;
+ int nfrags, frag_size;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ unsigned int i;
+ struct timeval tv;
+
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect boolean argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ nfrags = 20;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if (ss_to_waveformat(&ss, &wf) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+
+ if (record) {
+ if (waveInOpen(&hwi, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+ goto fail;
+ if (waveInStart(hwi) != MMSYSERR_NOERROR)
+ goto fail;
+ pa_log_debug(__FILE__": Opened waveIn subsystem.\n");
+ }
+
+ if (playback) {
+ if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+ goto fail;
+ pa_log_debug(__FILE__": Opened waveOut subsystem.\n");
+ }
+
+ InitializeCriticalSection(&u->crit);
+
+ if (hwi != INVALID_HANDLE_VALUE) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->notify = notify_source_cb;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Windows waveIn PCM");
+ } else
+ u->source = NULL;
+
+ if (hwo != INVALID_HANDLE_VALUE) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->notify = notify_sink_cb;
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Windows waveOut PCM");
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->core = c;
+ u->hwi = hwi;
+ u->hwo = hwo;
+
+ u->fragments = nfrags;
+ u->free_ifrags = u->fragments;
+ u->free_ofrags = u->fragments;
+ u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
+
+ u->written_bytes = 0;
+
+ u->oremain = u->fragment_size;
+
+ u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 3, &ss);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, u->poll_timeout);
+
+ u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u);
+ assert(u->event);
+
+ u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u);
+ assert(u->defer);
+ c->mainloop->defer_enable(u->defer, 0);
+
+ u->cur_ihdr = 0;
+ u->cur_ohdr = 0;
+ u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ihdrs);
+ u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ohdrs);
+ for (i = 0;i < u->fragments;i++) {
+ u->ihdrs[i].dwBufferLength = u->fragment_size;
+ u->ohdrs[i].dwBufferLength = u->fragment_size;
+ u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ihdrs);
+ u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ohdrs);
+ }
+
+ u->silence.length = u->fragment_size;
+ u->silence.memblock = pa_memblock_new(u->silence.length, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (hwi != INVALID_HANDLE_VALUE)
+ waveInClose(hwi);
+
+ if (hwo != INVALID_HANDLE_VALUE)
+ waveOutClose(hwo);
+
+ if (u)
+ pa_xfree(u);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ unsigned int i;
+
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->event)
+ c->mainloop->time_free(u->event);
+
+ if (u->defer)
+ c->mainloop->defer_free(u->defer);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->hwi != INVALID_HANDLE_VALUE) {
+ waveInReset(u->hwi);
+ waveInClose(u->hwi);
+ }
+
+ if (u->hwo != INVALID_HANDLE_VALUE) {
+ waveOutReset(u->hwo);
+ waveOutClose(u->hwo);
+ }
+
+ for (i = 0;i < u->fragments;i++) {
+ pa_xfree(u->ihdrs[i].lpData);
+ pa_xfree(u->ohdrs[i].lpData);
+ }
+
+ pa_xfree(u->ihdrs);
+ pa_xfree(u->ohdrs);
+
+ DeleteCriticalSection(&u->crit);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c
new file mode 100644
index 00000000..d722b732
--- /dev/null
+++ b/src/modules/module-x11-bell.c
@@ -0,0 +1,173 @@
+/* $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 <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/scache.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+#include <polypcore/x11wrap.h>
+
+#include "module-x11-bell-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("X11 Bell interceptor")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>")
+
+struct userdata {
+ pa_core *core;
+ int xkb_event_base;
+ char *sink_name;
+ char *scache_item;
+ Display *display;
+
+ pa_x11_wrapper *x11_wrapper;
+ pa_x11_client *x11_client;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "sample",
+ "display",
+ NULL
+};
+
+static int ring_bell(struct userdata *u, int percent) {
+ pa_sink *s;
+ pa_cvolume cv;
+ assert(u);
+
+ if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": Invalid sink: %s\n", u->sink_name);
+ return -1;
+ }
+
+ pa_scache_play_item(u->core, u->scache_item, s, pa_cvolume_set(&cv, PA_CHANNELS_MAX, percent*PA_VOLUME_NORM/100));
+ return 0;
+}
+
+static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
+ XkbBellNotifyEvent *bne;
+ struct userdata *u = userdata;
+ assert(w && e && u && u->x11_wrapper == w);
+
+ if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
+ return 0;
+
+ bne = (XkbBellNotifyEvent*) e;
+
+ if (ring_bell(u, bne->percent) < 0) {
+ pa_log_info(__FILE__": Ringing bell failed, reverting to X11 device bell.\n");
+ XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
+ }
+
+ return 1;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ pa_modargs *ma = NULL;
+ int major, minor;
+ unsigned int auto_ctrls, auto_values;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell"));
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->x11_client = NULL;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&major, &minor)) {
+ pa_log(__FILE__": XkbLibraryVersion() failed\n");
+ goto fail;
+ }
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+
+ if (!XkbQueryExtension(u->display, NULL, &u->xkb_event_base, NULL, &major, &minor)) {
+ pa_log(__FILE__": XkbQueryExtension() failed\n");
+ goto fail;
+ }
+
+ XkbSelectEvents(u->display, XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
+ auto_ctrls = auto_values = XkbAudibleBellMask;
+ XkbSetAutoResetControls(u->display, XkbAudibleBellMask, &auto_ctrls, &auto_values);
+ XkbChangeEnabledControls(u->display, XkbUseCoreKbd, XkbAudibleBellMask, 0);
+
+ u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+ if (m->userdata)
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u = m->userdata;
+ assert(c && m && u);
+
+ pa_xfree(u->scache_item);
+ pa_xfree(u->sink_name);
+
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c
new file mode 100644
index 00000000..dca5d049
--- /dev/null
+++ b/src/modules/module-x11-publish.c
@@ -0,0 +1,192 @@
+/* $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 <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <polypcore/module.h>
+#include <polypcore/sink.h>
+#include <polypcore/scache.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+#include <polypcore/x11wrap.h>
+#include <polypcore/util.h>
+#include <polypcore/native-common.h>
+#include <polypcore/authkey-prop.h>
+#include <polypcore/authkey.h>
+#include <polypcore/x11prop.h>
+#include <polypcore/strlist.h>
+#include <polypcore/props.h>
+
+#include "module-x11-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("X11 Credential Publisher")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("display=<X11 display>")
+
+static const char* const valid_modargs[] = {
+ "display",
+ "sink",
+ "source",
+ "cookie",
+ NULL
+};
+
+struct userdata {
+ pa_core *core;
+ pa_x11_wrapper *x11_wrapper;
+ Display *display;
+ char *id;
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+ int auth_cookie_in_property;
+};
+
+static int load_key(struct userdata *u, const char*fn) {
+ assert(u);
+
+ u->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug(__FILE__": using already loaded auth cookie.\n");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug(__FILE__": loading cookie from disk.\n");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ char hn[256], un[128];
+ char hx[PA_NATIVE_COOKIE_LENGTH*2+1];
+ const char *t;
+ char *s;
+ pa_strlist *l;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->id = NULL;
+ u->auth_cookie_in_property = 0;
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
+
+ if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME)))
+ goto fail;
+
+ s = pa_strlist_tostring(l);
+ pa_x11_set_prop(u->display, "POLYP_SERVER", s);
+ pa_xfree(s);
+
+ if (!pa_get_fqdn(hn, sizeof(hn)) || !pa_get_user_name(un, sizeof(un)))
+ goto fail;
+
+ u->id = pa_sprintf_malloc("%s@%s/%u", un, hn, (unsigned) getpid());
+ pa_x11_set_prop(u->display, "POLYP_ID", u->id);
+
+ if ((t = pa_modargs_get_value(ma, "source", NULL)))
+ pa_x11_set_prop(u->display, "POLYP_SOURCE", t);
+
+ if ((t = pa_modargs_get_value(ma, "sink", NULL)))
+ pa_x11_set_prop(u->display, "POLYP_SINK", t);
+
+ pa_x11_set_prop(u->display, "POLYP_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx)));
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata*u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->x11_wrapper) {
+ char t[256];
+
+ /* Yes, here is a race condition */
+ if (!pa_x11_get_prop(u->display, "POLYP_ID", t, sizeof(t)) || strcmp(t, u->id))
+ pa_log("WARNING: Polypaudio information vanished from X11!\n");
+ else {
+ pa_x11_del_prop(u->display, "POLYP_ID");
+ pa_x11_del_prop(u->display, "POLYP_SERVER");
+ pa_x11_del_prop(u->display, "POLYP_SINK");
+ pa_x11_del_prop(u->display, "POLYP_SOURCE");
+ pa_x11_del_prop(u->display, "POLYP_COOKIE");
+ XSync(u->display, False);
+ }
+ }
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+ pa_xfree(u->id);
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c
new file mode 100644
index 00000000..45d566ae
--- /dev/null
+++ b/src/modules/module-zeroconf-publish.c
@@ -0,0 +1,508 @@
+/* $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 <unistd.h>
+
+#include <polypcore/howl-wrap.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/autoload.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/native-common.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/dynarray.h>
+#include <polypcore/endianmacros.h>
+#include <polypcore/modargs.h>
+
+#include "module-zeroconf-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("port=<IP port number>")
+
+#define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
+#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
+#define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
+
+static const char* const valid_modargs[] = {
+ "port",
+ NULL
+};
+
+struct service {
+ sw_discovery_oid oid;
+ char *name;
+ int published; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
+
+ struct {
+ int valid;
+ pa_namereg_type type;
+ uint32_t index;
+ } loaded;
+
+ struct {
+ int valid;
+ pa_namereg_type type;
+ uint32_t index;
+ } autoload;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_howl_wrapper *howl_wrapper;
+ pa_hashmap *services;
+ pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
+ pa_subscription *subscription;
+
+ uint16_t port;
+ sw_discovery_oid server_oid;
+};
+
+static sw_result publish_reply(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) {
+ return SW_OKAY;
+}
+
+static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description, pa_typeid_t *ret_typeid) {
+ assert(u && s && s->loaded.valid && ret_ss && ret_description && ret_typeid);
+
+ if (s->loaded.type == PA_NAMEREG_SINK) {
+ pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
+ assert(sink);
+ *ret_ss = sink->sample_spec;
+ *ret_description = sink->description;
+ *ret_typeid = sink->typeid;
+ } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
+ pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
+ assert(source);
+ *ret_ss = source->sample_spec;
+ *ret_description = source->description;
+ *ret_typeid = source->typeid;
+ } else
+ assert(0);
+}
+
+static void txt_record_server_data(pa_core *c, sw_text_record t) {
+ char s[256];
+ assert(c);
+
+ sw_text_record_add_key_and_string_value(t, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
+ sw_text_record_add_key_and_string_value(t, "user-name", pa_get_user_name(s, sizeof(s)));
+ sw_text_record_add_key_and_string_value(t, "fqdn", pa_get_fqdn(s, sizeof(s)));
+ snprintf(s, sizeof(s), "0x%08x", c->cookie);
+ sw_text_record_add_key_and_string_value(t, "cookie", s);
+}
+
+static int publish_service(struct userdata *u, struct service *s) {
+ char t[256];
+ char hn[256];
+ int r = -1;
+ sw_text_record txt;
+ int free_txt = 0;
+ assert(u && s);
+
+ if ((s->published == 1 && s->loaded.valid) ||
+ (s->published == 2 && s->autoload.valid && !s->loaded.valid))
+ return 0;
+
+ if (s->published) {
+ sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
+ s->published = 0;
+ }
+
+ snprintf(t, sizeof(t), "Networked Audio Device %s on %s", s->name, pa_get_host_name(hn, sizeof(hn)));
+
+ if (sw_text_record_init(&txt) != SW_OKAY) {
+ pa_log(__FILE__": sw_text_record_init() failed\n");
+ goto finish;
+ }
+ free_txt = 1;
+
+ sw_text_record_add_key_and_string_value(txt, "device", s->name);
+
+ txt_record_server_data(u->core, txt);
+
+ if (s->loaded.valid) {
+ char z[64], *description;
+ pa_typeid_t typeid;
+ pa_sample_spec ss;
+
+ get_service_data(u, s, &ss, &description, &typeid);
+
+ snprintf(z, sizeof(z), "%u", ss.rate);
+ sw_text_record_add_key_and_string_value(txt, "rate", z);
+ snprintf(z, sizeof(z), "%u", ss.channels);
+ sw_text_record_add_key_and_string_value(txt, "channels", z);
+ sw_text_record_add_key_and_string_value(txt, "format", pa_sample_format_to_string(ss.format));
+
+ sw_text_record_add_key_and_string_value(txt, "description", description);
+
+ snprintf(z, sizeof(z), "0x%8x", typeid);
+ sw_text_record_add_key_and_string_value(txt, "typeid", z);
+
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ s->loaded.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, s, &s->oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register sink on zeroconf.\n");
+ goto finish;
+ }
+
+ s->published = 1;
+ } else if (s->autoload.valid) {
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ s->autoload.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, s, &s->oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register sink on zeroconf.\n");
+ goto finish;
+ }
+
+ s->published = 2;
+ }
+
+ r = 0;
+
+finish:
+
+ if (!s->published) {
+ /* Remove this service */
+ pa_hashmap_remove(u->services, s->name);
+ pa_xfree(s->name);
+ pa_xfree(s);
+ }
+
+ if (free_txt)
+ sw_text_record_fina(txt);
+
+ return r;
+}
+
+struct service *get_service(struct userdata *u, const char *name) {
+ struct service *s;
+
+ if ((s = pa_hashmap_get(u->services, name)))
+ return s;
+
+ s = pa_xmalloc(sizeof(struct service));
+ s->published = 0;
+ s->name = pa_xstrdup(name);
+ s->loaded.valid = s->autoload.valid = 0;
+
+ pa_hashmap_put(u->services, s->name, s);
+
+ return s;
+}
+
+static int publish_sink(struct userdata *u, pa_sink *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->loaded.valid)
+ return 0;
+
+ svc->loaded.valid = 1;
+ svc->loaded.type = PA_NAMEREG_SINK;
+ svc->loaded.index = s->index;
+
+ pa_dynarray_put(u->sink_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int publish_source(struct userdata *u, pa_source *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->loaded.valid)
+ return 0;
+
+ svc->loaded.valid = 1;
+ svc->loaded.type = PA_NAMEREG_SOURCE;
+ svc->loaded.index = s->index;
+
+ pa_dynarray_put(u->source_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->autoload.valid)
+ return 0;
+
+ svc->autoload.valid = 1;
+ svc->autoload.type = s->type;
+ svc->autoload.index = s->index;
+
+ pa_dynarray_put(u->autoload_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int remove_sink(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->sink_dynarray, index)))
+ return 0;
+
+ if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
+ return 0;
+
+ svc->loaded.valid = 0;
+ pa_dynarray_put(u->sink_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static int remove_source(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->source_dynarray, index)))
+ return 0;
+
+ if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
+ return 0;
+
+ svc->loaded.valid = 0;
+ pa_dynarray_put(u->source_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static int remove_autoload(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->autoload_dynarray, index)))
+ return 0;
+
+ if (!svc->autoload.valid)
+ return 0;
+
+ svc->autoload.valid = 0;
+ pa_dynarray_put(u->autoload_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static void subscribe_callback(pa_core *c, pa_subscription_event_type t, uint32_t index, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && c);
+
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
+ case PA_SUBSCRIPTION_EVENT_SINK: {
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_sink *sink;
+
+ if ((sink = pa_idxset_get_by_index(c->sinks, index))) {
+ if (publish_sink(u, sink) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_sink(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_source *source;
+
+ if ((source = pa_idxset_get_by_index(c->sources, index))) {
+ if (publish_source(u, source) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_source(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_autoload_entry *autoload;
+
+ if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, index))) {
+ if (publish_autoload(u, autoload) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_autoload(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+ }
+
+ return;
+
+fail:
+ if (u->subscription) {
+ pa_subscription_free(u->subscription);
+ u->subscription = NULL;
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ uint32_t index, port = PA_NATIVE_DEFAULT_PORT;
+ pa_sink *sink;
+ pa_source *source;
+ pa_autoload_entry *autoload;
+ pa_modargs *ma = NULL;
+ char t[256], hn[256];
+ int free_txt = 0;
+ sw_text_record txt;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
+ pa_log(__FILE__": invalid port specified.\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->port = (uint16_t) port;
+
+ if (!(u->howl_wrapper = pa_howl_wrapper_get(c)))
+ goto fail;
+
+ u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ u->sink_dynarray = pa_dynarray_new();
+ u->source_dynarray = pa_dynarray_new();
+ u->autoload_dynarray = pa_dynarray_new();
+
+ u->subscription = pa_subscription_new(c,
+ PA_SUBSCRIPTION_MASK_SINK|
+ PA_SUBSCRIPTION_MASK_SOURCE|
+ PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
+
+ for (sink = pa_idxset_first(c->sinks, &index); sink; sink = pa_idxset_next(c->sinks, &index))
+ if (publish_sink(u, sink) < 0)
+ goto fail;
+
+ for (source = pa_idxset_first(c->sources, &index); source; source = pa_idxset_next(c->sources, &index))
+ if (publish_source(u, source) < 0)
+ goto fail;
+
+ if (c->autoload_idxset)
+ for (autoload = pa_idxset_first(c->autoload_idxset, &index); autoload; autoload = pa_idxset_next(c->autoload_idxset, &index))
+ if (publish_autoload(u, autoload) < 0)
+ goto fail;
+
+ snprintf(t, sizeof(t), "Networked Audio Server on %s", pa_get_host_name(hn, sizeof(hn)));
+
+ if (sw_text_record_init(&txt) != SW_OKAY) {
+ pa_log(__FILE__": sw_text_record_init() failed\n");
+ goto fail;
+ }
+ free_txt = 1;
+
+ txt_record_server_data(u->core, txt);
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ SERVICE_NAME_SERVER,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, u, &u->server_oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register server on zeroconf.\n");
+ goto fail;
+ }
+
+ sw_text_record_fina(txt);
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (free_txt)
+ sw_text_record_fina(txt);
+
+ return -1;
+}
+
+static void service_free(void *p, void *userdata) {
+ struct service *s = p;
+ struct userdata *u = userdata;
+ assert(s && u);
+ sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
+ pa_xfree(s->name);
+ pa_xfree(s);
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata*u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->services)
+ pa_hashmap_free(u->services, service_free, u);
+
+ if (u->sink_dynarray)
+ pa_dynarray_free(u->sink_dynarray, NULL, NULL);
+ if (u->source_dynarray)
+ pa_dynarray_free(u->source_dynarray, NULL, NULL);
+ if (u->autoload_dynarray)
+ pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ if (u->howl_wrapper)
+ pa_howl_wrapper_unref(u->howl_wrapper);
+
+
+ pa_xfree(u);
+}
+
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-&g