diff options
author | Sjoerd Simons <sjoerd@debian.org> | 2009-11-23 22:22:41 +0000 |
---|---|---|
committer | Sjoerd Simons <sjoerd@debian.org> | 2009-11-23 22:22:41 +0000 |
commit | 0201790155d0719dfdd4785a06a5d5f06cfcd7c1 (patch) | |
tree | 4db36c57353b8a93c511334c0ea11655424ca242 /src | |
parent | 0fd5a5243b25fb9fcbf554e4db7fb448d799d40a (diff) |
Imported Upstream version 0.9.21
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 86 | ||||
-rw-r--r-- | src/Makefile.in | 186 | ||||
-rw-r--r-- | src/daemon/main.c | 37 | ||||
-rw-r--r-- | src/daemon/pulseaudio-kde.desktop.in | 11 | ||||
-rwxr-xr-x | src/daemon/start-pulseaudio-kde.in | 30 | ||||
-rw-r--r-- | src/map-file | 8 | ||||
-rw-r--r-- | src/modules/alsa/alsa-sink.c | 2 | ||||
-rw-r--r-- | src/modules/alsa/alsa-source.c | 2 | ||||
-rw-r--r-- | src/modules/alsa/alsa-util.c | 16 | ||||
-rw-r--r-- | src/modules/module-device-manager-symdef.h | 29 | ||||
-rw-r--r-- | src/modules/module-device-manager.c | 1540 | ||||
-rw-r--r-- | src/modules/module-rygel-media-server.c | 45 | ||||
-rw-r--r-- | src/pulse/context.c | 5 | ||||
-rw-r--r-- | src/pulse/ext-device-manager.c | 437 | ||||
-rw-r--r-- | src/pulse/ext-device-manager.h | 128 | ||||
-rw-r--r-- | src/pulse/internal.h | 6 | ||||
-rw-r--r-- | src/pulse/stream.c | 59 | ||||
-rw-r--r-- | src/pulse/version.h | 4 |
18 files changed, 2490 insertions, 141 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index de15a8f..598e77f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,25 +107,12 @@ MODULE_LDFLAGS = -module -disable-static -avoid-version $(LDFLAGS_NOUNDEFINED) # Extra files # ################################### -EXTRA_DIST = \ - pulse/client.conf.in \ - pulse/version.h.in \ - daemon/daemon.conf.in \ - daemon/default.pa.in \ - daemon/system.pa.in \ - daemon/default.pa.win32 \ - depmod.py \ - daemon/esdcompat.in \ - daemon/start-pulseaudio-x11.in \ - utils/padsp \ - modules/module-defs.h.m4 \ - daemon/pulseaudio.desktop.in \ - map-file \ - daemon/pulseaudio-system.conf \ +ALSA_PROFILES = \ modules/alsa/mixer/profile-sets/default.conf \ modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ - modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \ - modules/alsa/mixer/profile-sets/90-pulseaudio.rules \ + modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf + +ALSA_PATHS = \ modules/alsa/mixer/paths/analog-input-aux.conf \ modules/alsa/mixer/paths/analog-input.conf \ modules/alsa/mixer/paths/analog-input.conf.common \ @@ -144,6 +131,27 @@ EXTRA_DIST = \ modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ modules/alsa/mixer/paths/analog-output-mono.conf +EXTRA_DIST = \ + pulse/client.conf.in \ + pulse/version.h.in \ + daemon/daemon.conf.in \ + daemon/default.pa.in \ + daemon/system.pa.in \ + daemon/default.pa.win32 \ + depmod.py \ + daemon/esdcompat.in \ + daemon/start-pulseaudio-x11.in \ + daemon/start-pulseaudio-kde.in \ + utils/padsp \ + modules/module-defs.h.m4 \ + daemon/pulseaudio.desktop.in \ + daemon/pulseaudio-kde.desktop.in \ + map-file \ + daemon/pulseaudio-system.conf \ + modules/alsa/mixer/profile-sets/90-pulseaudio.rules \ + ${ALSA_PROFILES} \ + ${ALSA_PATHS} + pulseconf_DATA = \ default.pa \ system.pa \ @@ -155,7 +163,8 @@ dbuspolicy_DATA = \ if HAVE_X11 xdgautostart_in_files = \ - daemon/pulseaudio.desktop.in + daemon/pulseaudio.desktop.in \ + daemon/pulseaudio-kde.desktop.in endif xdgautostart_DATA = $(xdgautostart_in_files:.desktop.in=.desktop) @INTLTOOL_DESKTOP_RULE@ @@ -217,7 +226,7 @@ if HAVE_AVAHI bin_PROGRAMS += pabrowse endif -bin_SCRIPTS = esdcompat start-pulseaudio-x11 +bin_SCRIPTS = esdcompat start-pulseaudio-x11 start-pulseaudio-kde pacat_SOURCES = utils/pacat.c pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) @@ -691,6 +700,7 @@ pulseinclude_HEADERS = \ pulse/context.h \ pulse/def.h \ pulse/error.h \ + pulse/ext-device-manager.h \ pulse/ext-stream-restore.h \ pulse/gccmacro.h \ pulse/introspect.h \ @@ -741,6 +751,7 @@ libpulse_la_SOURCES = \ pulse/context.c pulse/context.h \ pulse/def.h \ pulse/error.c pulse/error.h \ + pulse/ext-device-manager.c pulse/ext-device-manager.h \ pulse/ext-stream-restore.c pulse/ext-stream-restore.h \ pulse/gccmacro.h \ pulse/internal.h \ @@ -992,6 +1003,7 @@ modlibexec_LTLIBRARIES += \ module-sine-source.la \ module-detect.la \ module-volume-restore.la \ + module-device-manager.la \ module-device-restore.la \ module-stream-restore.la \ module-card-restore.la \ @@ -1072,32 +1084,14 @@ modlibexec_LTLIBRARIES += \ module-alsa-source.la \ module-alsa-card.la -alsaprofilesets_DATA = \ - modules/alsa/mixer/profile-sets/default.conf \ - modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ - modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf +alsaprofilesets_DATA = ${ALSA_PROFILES} if HAVE_UDEV udevrules_DATA = \ modules/alsa/mixer/profile-sets/90-pulseaudio.rules endif -alsapaths_DATA = \ - modules/alsa/mixer/paths/analog-input-aux.conf \ - modules/alsa/mixer/paths/analog-input.conf \ - modules/alsa/mixer/paths/analog-input.conf.common \ - modules/alsa/mixer/paths/analog-input-fm.conf \ - modules/alsa/mixer/paths/analog-input-linein.conf \ - modules/alsa/mixer/paths/analog-input-mic.conf \ - modules/alsa/mixer/paths/analog-input-mic.conf.common \ - modules/alsa/mixer/paths/analog-input-mic-line.conf \ - modules/alsa/mixer/paths/analog-input-tvtuner.conf \ - modules/alsa/mixer/paths/analog-input-video.conf \ - modules/alsa/mixer/paths/analog-output.conf \ - modules/alsa/mixer/paths/analog-output.conf.common \ - modules/alsa/mixer/paths/analog-output-headphones.conf \ - modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ - modules/alsa/mixer/paths/analog-output-mono.conf +alsapaths_DATA = ${ALSA_PATHS} endif @@ -1233,6 +1227,7 @@ SYMDEF_FILES = \ modules/jack/module-jack-sink-symdef.h \ modules/jack/module-jack-source-symdef.h \ modules/module-volume-restore-symdef.h \ + modules/module-device-manager-symdef.h \ modules/module-device-restore-symdef.h \ modules/module-stream-restore-symdef.h \ modules/module-card-restore-symdef.h \ @@ -1541,6 +1536,12 @@ module_cork_music_on_phone_la_LDFLAGS = $(MODULE_LDFLAGS) module_cork_music_on_phone_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_cork_music_on_phone_la_CFLAGS = $(AM_CFLAGS) +# Device description restore module +module_device_manager_la_SOURCES = modules/module-device-manager.c +module_device_manager_la_LDFLAGS = $(MODULE_LDFLAGS) +module_device_manager_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_device_manager_la_CFLAGS = $(AM_CFLAGS) + # Device volume/muted restore module module_device_restore_la_SOURCES = modules/module-device-restore.c module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -1703,7 +1704,7 @@ module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) # Some minor stuff # ################################### -CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 daemon/pulseaudio.desktop +CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 start-pulseaudio-kde daemon/pulseaudio.desktop daemon/pulseaudio-kde.desktop esdcompat: daemon/esdcompat.in Makefile sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ @@ -1716,6 +1717,11 @@ start-pulseaudio-x11: daemon/start-pulseaudio-x11.in Makefile -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ chmod +x start-pulseaudio-x11 +start-pulseaudio-kde: daemon/start-pulseaudio-kde.in Makefile + sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' \ + -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ + chmod +x start-pulseaudio-kde + client.conf: pulse/client.conf.in Makefile sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ diff --git a/src/Makefile.in b/src/Makefile.in index 238e18d..73a9959 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -516,7 +516,8 @@ libpulse_la_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) libpulsecommon-@PA_MAJORMINORMICRO@.la am__libpulse_la_SOURCES_DIST = pulse/cdecl.h pulse/channelmap.c \ pulse/channelmap.h pulse/context.c pulse/context.h pulse/def.h \ - pulse/error.c pulse/error.h pulse/ext-stream-restore.c \ + pulse/error.c pulse/error.h pulse/ext-device-manager.c \ + pulse/ext-device-manager.h pulse/ext-stream-restore.c \ pulse/ext-stream-restore.h pulse/gccmacro.h pulse/internal.h \ pulse/introspect.c pulse/introspect.h pulse/mainloop-api.c \ pulse/mainloop-api.h pulse/mainloop-signal.c \ @@ -533,6 +534,7 @@ am__libpulse_la_SOURCES_DIST = pulse/cdecl.h pulse/channelmap.c \ @HAVE_X11_TRUE@am__objects_4 = libpulse_la-client-conf-x11.lo am_libpulse_la_OBJECTS = libpulse_la-channelmap.lo \ libpulse_la-context.lo libpulse_la-error.lo \ + libpulse_la-ext-device-manager.lo \ libpulse_la-ext-stream-restore.lo libpulse_la-introspect.lo \ libpulse_la-mainloop-api.lo libpulse_la-mainloop-signal.lo \ libpulse_la-mainloop.lo libpulse_la-operation.lo \ @@ -1024,6 +1026,17 @@ module_detect_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(module_detect_la_CFLAGS) $(CFLAGS) \ $(module_detect_la_LDFLAGS) $(LDFLAGS) -o $@ +module_device_manager_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \ + libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la \ + libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +am_module_device_manager_la_OBJECTS = \ + module_device_manager_la-module-device-manager.lo +module_device_manager_la_OBJECTS = \ + $(am_module_device_manager_la_OBJECTS) +module_device_manager_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(module_device_manager_la_CFLAGS) $(CFLAGS) \ + $(module_device_manager_la_LDFLAGS) $(LDFLAGS) -o $@ module_device_restore_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \ libpulsecore-@PA_MAJORMINORMICRO@.la \ libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -2086,6 +2099,7 @@ SOURCES = $(libalsa_util_la_SOURCES) $(libavahi_wrap_la_SOURCES) \ $(module_cork_music_on_phone_la_SOURCES) \ $(module_default_device_restore_la_SOURCES) \ $(module_detect_la_SOURCES) \ + $(module_device_manager_la_SOURCES) \ $(module_device_restore_la_SOURCES) \ $(module_esound_compat_spawnfd_la_SOURCES) \ $(module_esound_compat_spawnpid_la_SOURCES) \ @@ -2181,6 +2195,7 @@ DIST_SOURCES = $(am__libalsa_util_la_SOURCES_DIST) \ $(module_cork_music_on_phone_la_SOURCES) \ $(module_default_device_restore_la_SOURCES) \ $(module_detect_la_SOURCES) \ + $(module_device_manager_la_SOURCES) \ $(module_device_restore_la_SOURCES) \ $(module_esound_compat_spawnfd_la_SOURCES) \ $(module_esound_compat_spawnpid_la_SOURCES) \ @@ -2251,14 +2266,14 @@ DATA = $(alsapaths_DATA) $(alsaprofilesets_DATA) $(dbuspolicy_DATA) \ $(pulseconf_DATA) $(udevrules_DATA) $(xdgautostart_DATA) am__pulseinclude_HEADERS_DIST = pulse/cdecl.h pulse/channelmap.h \ pulse/context.h pulse/def.h pulse/error.h \ - pulse/ext-stream-restore.h pulse/gccmacro.h pulse/introspect.h \ - pulse/mainloop-api.h pulse/mainloop-signal.h pulse/mainloop.h \ - pulse/operation.h pulse/proplist.h pulse/pulseaudio.h \ - pulse/rtclock.h pulse/sample.h pulse/scache.h pulse/simple.h \ - pulse/stream.h pulse/subscribe.h pulse/thread-mainloop.h \ - pulse/timeval.h pulse/utf8.h pulse/util.h pulse/version.h \ - pulse/volume.h pulse/xmalloc.h pulse/browser.h \ - pulse/glib-mainloop.h + pulse/ext-device-manager.h pulse/ext-stream-restore.h \ + pulse/gccmacro.h pulse/introspect.h pulse/mainloop-api.h \ + pulse/mainloop-signal.h pulse/mainloop.h pulse/operation.h \ + pulse/proplist.h pulse/pulseaudio.h pulse/rtclock.h \ + pulse/sample.h pulse/scache.h pulse/simple.h pulse/stream.h \ + pulse/subscribe.h pulse/thread-mainloop.h pulse/timeval.h \ + pulse/utf8.h pulse/util.h pulse/version.h pulse/volume.h \ + pulse/xmalloc.h pulse/browser.h pulse/glib-mainloop.h HEADERS = $(pulseinclude_HEADERS) ETAGS = etags CTAGS = ctags @@ -2564,34 +2579,39 @@ MODULE_LDFLAGS = -module -disable-static -avoid-version $(LDFLAGS_NOUNDEFINED) ################################### # Extra files # ################################### +ALSA_PROFILES = \ + modules/alsa/mixer/profile-sets/default.conf \ + modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ + modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf + +ALSA_PATHS = \ + modules/alsa/mixer/paths/analog-input-aux.conf \ + modules/alsa/mixer/paths/analog-input.conf \ + modules/alsa/mixer/paths/analog-input.conf.common \ + modules/alsa/mixer/paths/analog-input-fm.conf \ + modules/alsa/mixer/paths/analog-input-linein.conf \ + modules/alsa/mixer/paths/analog-input-mic.conf \ + modules/alsa/mixer/paths/analog-input-mic.conf.common \ + modules/alsa/mixer/paths/analog-input-mic-line.conf \ + modules/alsa/mixer/paths/analog-input-tvtuner.conf \ + modules/alsa/mixer/paths/analog-input-video.conf \ + modules/alsa/mixer/paths/analog-output.conf \ + modules/alsa/mixer/paths/analog-output-speaker.conf \ + modules/alsa/mixer/paths/analog-output.conf.common \ + modules/alsa/mixer/paths/analog-output-headphones.conf \ + modules/alsa/mixer/paths/analog-output-headphones-2.conf \ + modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ + modules/alsa/mixer/paths/analog-output-mono.conf + EXTRA_DIST = pulse/client.conf.in pulse/version.h.in \ daemon/daemon.conf.in daemon/default.pa.in daemon/system.pa.in \ daemon/default.pa.win32 depmod.py daemon/esdcompat.in \ - daemon/start-pulseaudio-x11.in utils/padsp \ - modules/module-defs.h.m4 daemon/pulseaudio.desktop.in map-file \ - daemon/pulseaudio-system.conf \ - modules/alsa/mixer/profile-sets/default.conf \ - modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ - modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \ + daemon/start-pulseaudio-x11.in daemon/start-pulseaudio-kde.in \ + utils/padsp modules/module-defs.h.m4 \ + daemon/pulseaudio.desktop.in daemon/pulseaudio-kde.desktop.in \ + map-file daemon/pulseaudio-system.conf \ modules/alsa/mixer/profile-sets/90-pulseaudio.rules \ - modules/alsa/mixer/paths/analog-input-aux.conf \ - modules/alsa/mixer/paths/analog-input.conf \ - modules/alsa/mixer/paths/analog-input.conf.common \ - modules/alsa/mixer/paths/analog-input-fm.conf \ - modules/alsa/mixer/paths/analog-input-linein.conf \ - modules/alsa/mixer/paths/analog-input-mic.conf \ - modules/alsa/mixer/paths/analog-input-mic.conf.common \ - modules/alsa/mixer/paths/analog-input-mic-line.conf \ - modules/alsa/mixer/paths/analog-input-tvtuner.conf \ - modules/alsa/mixer/paths/analog-input-video.conf \ - modules/alsa/mixer/paths/analog-output.conf \ - modules/alsa/mixer/paths/analog-output-speaker.conf \ - modules/alsa/mixer/paths/analog-output.conf.common \ - modules/alsa/mixer/paths/analog-output-headphones.conf \ - modules/alsa/mixer/paths/analog-output-headphones-2.conf \ - modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ - modules/alsa/mixer/paths/analog-output-mono.conf \ - $(SYMDEF_FILES) + ${ALSA_PROFILES} ${ALSA_PATHS} $(SYMDEF_FILES) pulseconf_DATA = \ default.pa \ system.pa \ @@ -2602,7 +2622,8 @@ dbuspolicy_DATA = \ daemon/pulseaudio-system.conf @HAVE_X11_TRUE@xdgautostart_in_files = \ -@HAVE_X11_TRUE@ daemon/pulseaudio.desktop.in +@HAVE_X11_TRUE@ daemon/pulseaudio.desktop.in \ +@HAVE_X11_TRUE@ daemon/pulseaudio-kde.desktop.in xdgautostart_DATA = $(xdgautostart_in_files:.desktop.in=.desktop) BUILT_SOURCES = pulse/version.h $(SYMDEF_FILES) @@ -2623,7 +2644,8 @@ pulseaudio_DEPENDENCIES = libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@P @PREOPEN_MODS_TRUE@PREOPEN_LIBS = $(PREOPEN_MODS) @FORCE_PREOPEN_FALSE@pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(IMMEDIATE_LDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f)) @FORCE_PREOPEN_TRUE@pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(IMMEDIATE_LDFLAGS) -dlpreopen force $(foreach f,$(PREOPEN_LIBS),-dlpreopen $(f)) -bin_SCRIPTS = esdcompat start-pulseaudio-x11 $(am__append_29) +bin_SCRIPTS = esdcompat start-pulseaudio-x11 start-pulseaudio-kde \ + $(am__append_29) pacat_SOURCES = utils/pacat.c pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) pacat_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) @@ -2900,19 +2922,20 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) \ ################################### pulseinclude_HEADERS = pulse/cdecl.h pulse/channelmap.h \ pulse/context.h pulse/def.h pulse/error.h \ - pulse/ext-stream-restore.h pulse/gccmacro.h pulse/introspect.h \ - pulse/mainloop-api.h pulse/mainloop-signal.h pulse/mainloop.h \ - pulse/operation.h pulse/proplist.h pulse/pulseaudio.h \ - pulse/rtclock.h pulse/sample.h pulse/scache.h pulse/simple.h \ - pulse/stream.h pulse/subscribe.h pulse/thread-mainloop.h \ - pulse/timeval.h pulse/utf8.h pulse/util.h pulse/version.h \ - pulse/volume.h pulse/xmalloc.h $(am__append_21) \ - $(am__append_23) + pulse/ext-device-manager.h pulse/ext-stream-restore.h \ + pulse/gccmacro.h pulse/introspect.h pulse/mainloop-api.h \ + pulse/mainloop-signal.h pulse/mainloop.h pulse/operation.h \ + pulse/proplist.h pulse/pulseaudio.h pulse/rtclock.h \ + pulse/sample.h pulse/scache.h pulse/simple.h pulse/stream.h \ + pulse/subscribe.h pulse/thread-mainloop.h pulse/timeval.h \ + pulse/utf8.h pulse/util.h pulse/version.h pulse/volume.h \ + pulse/xmalloc.h $(am__append_21) $(am__append_23) # Public interface libpulse_la_SOURCES = pulse/cdecl.h pulse/channelmap.c \ pulse/channelmap.h pulse/context.c pulse/context.h pulse/def.h \ - pulse/error.c pulse/error.h pulse/ext-stream-restore.c \ + pulse/error.c pulse/error.h pulse/ext-device-manager.c \ + pulse/ext-device-manager.h pulse/ext-stream-restore.c \ pulse/ext-stream-restore.h pulse/gccmacro.h pulse/internal.h \ pulse/introspect.c pulse/introspect.h pulse/mainloop-api.c \ pulse/mainloop-api.h pulse/mainloop-signal.c \ @@ -3023,14 +3046,15 @@ modlibexec_LTLIBRARIES = libcli.la libprotocol-cli.la \ $(am__append_44) $(am__append_45) module-cli.la \ module-cli-protocol-tcp.la module-simple-protocol-tcp.la \ module-null-sink.la module-sine-source.la module-detect.la \ - module-volume-restore.la module-device-restore.la \ - module-stream-restore.la module-card-restore.la \ - module-default-device-restore.la module-always-sink.la \ - module-rescue-streams.la module-intended-roles.la \ - module-suspend-on-idle.la module-http-protocol-tcp.la \ - module-sine.la module-native-protocol-tcp.la \ - module-native-protocol-fd.la module-esound-protocol-tcp.la \ - module-combine.la module-remap-sink.la module-ladspa-sink.la \ + module-volume-restore.la module-device-manager.la \ + module-device-restore.la module-stream-restore.la \ + module-card-restore.la module-default-device-restore.la \ + module-always-sink.la module-rescue-streams.la \ + module-intended-roles.la module-suspend-on-idle.la \ + module-http-protocol-tcp.la module-sine.la \ + module-native-protocol-tcp.la module-native-protocol-fd.la \ + module-esound-protocol-tcp.la module-combine.la \ + module-remap-sink.la module-ladspa-sink.la \ module-esound-sink.la module-tunnel-sink.la \ module-tunnel-source.la module-position-event-sounds.la \ module-augment-properties.la module-cork-music-on-phone.la \ @@ -3082,31 +3106,11 @@ libavahi_wrap_la_SOURCES = pulsecore/avahi-wrap.c pulsecore/avahi-wrap.h libavahi_wrap_la_LDFLAGS = -avoid-version libavahi_wrap_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -@HAVE_ALSA_TRUE@alsaprofilesets_DATA = \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/default.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf - +@HAVE_ALSA_TRUE@alsaprofilesets_DATA = ${ALSA_PROFILES} @HAVE_ALSA_TRUE@@HAVE_UDEV_TRUE@udevrules_DATA = \ @HAVE_ALSA_TRUE@@HAVE_UDEV_TRUE@ modules/alsa/mixer/profile-sets/90-pulseaudio.rules -@HAVE_ALSA_TRUE@alsapaths_DATA = \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-aux.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input.conf.common \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-fm.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-linein.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-mic.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-mic.conf.common \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-mic-line.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-tvtuner.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-input-video.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-output.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-output.conf.common \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-output-headphones.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ -@HAVE_ALSA_TRUE@ modules/alsa/mixer/paths/analog-output-mono.conf - +@HAVE_ALSA_TRUE@alsapaths_DATA = ${ALSA_PATHS} # These are generated by an M4 script SYMDEF_FILES = \ @@ -3157,6 +3161,7 @@ SYMDEF_FILES = \ modules/jack/module-jack-sink-symdef.h \ modules/jack/module-jack-source-symdef.h \ modules/module-volume-restore-symdef.h \ + modules/module-device-manager-symdef.h \ modules/module-device-restore-symdef.h \ modules/module-stream-restore-symdef.h \ modules/module-card-restore-symdef.h \ @@ -3409,6 +3414,12 @@ module_cork_music_on_phone_la_LDFLAGS = $(MODULE_LDFLAGS) module_cork_music_on_phone_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_cork_music_on_phone_la_CFLAGS = $(AM_CFLAGS) +# Device description restore module +module_device_manager_la_SOURCES = modules/module-device-manager.c +module_device_manager_la_LDFLAGS = $(MODULE_LDFLAGS) +module_device_manager_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_device_manager_la_CFLAGS = $(AM_CFLAGS) + # Device volume/muted restore module module_device_restore_la_SOURCES = modules/module-device-restore.c module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS) @@ -3554,7 +3565,7 @@ module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) ################################### # Some minor stuff # ################################### -CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 daemon/pulseaudio.desktop +CLEANFILES = esdcompat client.conf default.pa system.pa daemon.conf start-pulseaudio-x11 start-pulseaudio-kde daemon/pulseaudio.desktop daemon/pulseaudio-kde.desktop all: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) all-am @@ -3739,6 +3750,8 @@ module-default-device-restore.la: $(module_default_device_restore_la_OBJECTS) $( $(AM_V_CCLD)$(module_default_device_restore_la_LINK) -rpath $(modlibexecdir) $(module_default_device_restore_la_OBJECTS) $(module_default_device_restore_la_LIBADD) $(LIBS) module-detect.la: $(module_detect_la_OBJECTS) $(module_detect_la_DEPENDENCIES) $(AM_V_CCLD)$(module_detect_la_LINK) -rpath $(modlibexecdir) $(module_detect_la_OBJECTS) $(module_detect_la_LIBADD) $(LIBS) +module-device-manager.la: $(module_device_manager_la_OBJECTS) $(module_device_manager_la_DEPENDENCIES) + $(AM_V_CCLD)$(module_device_manager_la_LINK) -rpath $(modlibexecdir) $(module_device_manager_la_OBJECTS) $(module_device_manager_la_LIBADD) $(LIBS) module-device-restore.la: $(module_device_restore_la_OBJECTS) $(module_device_restore_la_DEPENDENCIES) $(AM_V_CCLD)$(module_device_restore_la_LINK) -rpath $(modlibexecdir) $(module_device_restore_la_OBJECTS) $(module_device_restore_la_LIBADD) $(LIBS) module-esound-compat-spawnfd.la: $(module_esound_compat_spawnfd_la_OBJECTS) $(module_esound_compat_spawnfd_la_DEPENDENCIES) @@ -4178,6 +4191,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-client-conf-x11.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-context.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-error.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-ext-device-manager.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-ext-stream-restore.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-introspect.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpulse_la-mainloop-api.Plo@am__quote@ @@ -4359,6 +4373,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_cork_music_on_phone_la-module-cork-music-on-phone.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_default_device_restore_la-module-default-device-restore.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_detect_la-module-detect.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_device_manager_la-module-device-manager.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_device_restore_la-module-device-restore.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_esound_protocol_tcp_la-module-protocol-stub.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module_esound_protocol_unix_la-module-protocol-stub.Plo@am__quote@ @@ -4713,6 +4728,14 @@ libpulse_la-error.lo: pulse/error.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulse_la_CFLAGS) $(CFLAGS) -c -o libpulse_la-error.lo `test -f 'pulse/error.c' || echo '$(srcdir)/'`pulse/error.c +libpulse_la-ext-device-manager.lo: pulse/ext-device-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulse_la_CFLAGS) $(CFLAGS) -MT libpulse_la-ext-device-manager.lo -MD -MP -MF $(DEPDIR)/libpulse_la-ext-device-manager.Tpo -c -o libpulse_la-ext-device-manager.lo `test -f 'pulse/ext-device-manager.c' || echo '$(srcdir)/'`pulse/ext-device-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpulse_la-ext-device-manager.Tpo $(DEPDIR)/libpulse_la-ext-device-manager.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='pulse/ext-device-manager.c' object='libpulse_la-ext-device-manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulse_la_CFLAGS) $(CFLAGS) -c -o libpulse_la-ext-device-manager.lo `test -f 'pulse/ext-device-manager.c' || echo '$(srcdir)/'`pulse/ext-device-manager.c + libpulse_la-ext-stream-restore.lo: pulse/ext-stream-restore.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpulse_la_CFLAGS) $(CFLAGS) -MT libpulse_la-ext-stream-restore.lo -MD -MP -MF $(DEPDIR)/libpulse_la-ext-stream-restore.Tpo -c -o libpulse_la-ext-stream-restore.lo `test -f 'pulse/ext-stream-restore.c' || echo '$(srcdir)/'`pulse/ext-stream-restore.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpulse_la-ext-stream-restore.Tpo $(DEPDIR)/libpulse_la-ext-stream-restore.Plo @@ -6009,6 +6032,14 @@ module_detect_la-module-detect.lo: modules/module-detect.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_detect_la_CFLAGS) $(CFLAGS) -c -o module_detect_la-module-detect.lo `test -f 'modules/module-detect.c' || echo '$(srcdir)/'`modules/module-detect.c +module_device_manager_la-module-device-manager.lo: modules/module-device-manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_device_manager_la_CFLAGS) $(CFLAGS) -MT module_device_manager_la-module-device-manager.lo -MD -MP -MF $(DEPDIR)/module_device_manager_la-module-device-manager.Tpo -c -o module_device_manager_la-module-device-manager.lo `test -f 'modules/module-device-manager.c' || echo '$(srcdir)/'`modules/module-device-manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/module_device_manager_la-module-device-manager.Tpo $(DEPDIR)/module_device_manager_la-module-device-manager.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='modules/module-device-manager.c' object='module_device_manager_la-module-device-manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_device_manager_la_CFLAGS) $(CFLAGS) -c -o module_device_manager_la-module-device-manager.lo `test -f 'modules/module-device-manager.c' || echo '$(srcdir)/'`modules/module-device-manager.c + module_device_restore_la-module-device-restore.lo: modules/module-device-restore.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(module_device_restore_la_CFLAGS) $(CFLAGS) -MT module_device_restore_la-module-device-restore.lo -MD -MP -MF $(DEPDIR)/module_device_restore_la-module-device-restore.Tpo -c -o module_device_restore_la-module-device-restore.lo `test -f 'modules/module-device-restore.c' || echo '$(srcdir)/'`modules/module-device-restore.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/module_device_restore_la-module-device-restore.Tpo $(DEPDIR)/module_device_restore_la-module-device-restore.Plo @@ -7855,6 +7886,11 @@ start-pulseaudio-x11: daemon/start-pulseaudio-x11.in Makefile -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ chmod +x start-pulseaudio-x11 +start-pulseaudio-kde: daemon/start-pulseaudio-kde.in Makefile + sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' \ + -e 's,@PACTL_BINARY\@,$(bindir)/pactl,g' < $< > $@ + chmod +x start-pulseaudio-kde + client.conf: pulse/client.conf.in Makefile sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ diff --git a/src/daemon/main.c b/src/daemon/main.c index c006bad..eafd72a 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -509,6 +509,12 @@ int main(int argc, char *argv[]) { goto finish; case PA_CMD_DUMP_CONF: { + + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + s = pa_daemon_conf_dump(conf); fputs(s, stdout); pa_xfree(s); @@ -519,6 +525,11 @@ int main(int argc, char *argv[]) { case PA_CMD_DUMP_RESAMPLE_METHODS: { int i; + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + for (i = 0; i < PA_RESAMPLER_MAX; i++) if (pa_resample_method_supported(i)) printf("%s\n", pa_resample_method_to_string(i)); @@ -533,6 +544,12 @@ int main(int argc, char *argv[]) { goto finish; case PA_CMD_VERSION : + + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + printf(PACKAGE_NAME" "PACKAGE_VERSION"\n"); retval = 0; goto finish; @@ -540,6 +557,11 @@ int main(int argc, char *argv[]) { case PA_CMD_CHECK: { pid_t pid; + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) pa_log_info(_("Daemon not running")); else { @@ -552,6 +574,11 @@ int main(int argc, char *argv[]) { } case PA_CMD_KILL: + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + if (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0) pa_log(_("Failed to kill daemon: %s"), pa_cstrerror(errno)); else @@ -561,6 +588,11 @@ int main(int argc, char *argv[]) { case PA_CMD_CLEANUP_SHM: + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + if (pa_shm_cleanup() >= 0) retval = 0; @@ -570,6 +602,11 @@ int main(int argc, char *argv[]) { pa_assert(conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START); } + if (d < argc) { + pa_log("Too many arguments.\n"); + goto finish; + } + if (getuid() == 0 && !conf->system_instance) pa_log_warn(_("This program is not intended to be run as root (unless --system is specified).")); else if (getuid() != 0 && conf->system_instance) { diff --git a/src/daemon/pulseaudio-kde.desktop.in b/src/daemon/pulseaudio-kde.desktop.in new file mode 100644 index 0000000..0684642 --- /dev/null +++ b/src/daemon/pulseaudio-kde.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +_Name=PulseAudio Sound System KDE Routing Policy +_Comment=Start the PulseAudio Sound System with KDE Routing Policy +Exec=start-pulseaudio-kde +Terminal=false +Type=Application +Categories= +GenericName= +OnlyShowIn=KDE; diff --git a/src/daemon/start-pulseaudio-kde.in b/src/daemon/start-pulseaudio-kde.in new file mode 100755 index 0000000..c319e7d --- /dev/null +++ b/src/daemon/start-pulseaudio-kde.in @@ -0,0 +1,30 @@ +#!/bin/sh + +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio 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 PulseAudio; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +set -e + +[ -z "$PULSE_SERVER" ] + +@PA_BINARY@ --start "$@" + +if [ x"$DISPLAY" != x ] ; then + + @PACTL_BINARY@ load-module module-device-manager "do_routing=1" > /dev/null + +fi diff --git a/src/map-file b/src/map-file index 95b2803..3fc934c 100644 --- a/src/map-file +++ b/src/map-file @@ -144,6 +144,14 @@ pa_cvolume_set_fade; pa_cvolume_set_position; pa_cvolume_snprint; pa_cvolume_valid; +pa_ext_device_manager_delete; +pa_ext_device_manager_enable_role_device_priority_routing; +pa_ext_device_manager_read; +pa_ext_device_manager_reorder_devices_for_role; +pa_ext_device_manager_set_device_description; +pa_ext_device_manager_set_subscribe_cb; +pa_ext_device_manager_subscribe; +pa_ext_device_manager_test; pa_ext_stream_restore_delete; pa_ext_stream_restore_read; pa_ext_stream_restore_set_subscribe_cb; diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 856adb1..ed16c83 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -983,7 +983,7 @@ static int unsuspend(struct userdata *u) { buffer_size*u->frame_size != u->hwbuf_size) { pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, - (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size)); + (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size)); goto fail; } diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index e775b20..157698e 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -930,7 +930,7 @@ static int unsuspend(struct userdata *u) { buffer_size*u->frame_size != u->hwbuf_size) { pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, - (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size)); + (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size)); goto fail; } diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index b8d1357..52f1259 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -259,6 +259,10 @@ int pa_alsa_set_hw_params( goto finish; } + /* We ignore very small sampling rate deviations */ + if (_ss.rate >= ss->rate*.95 && _ss.rate <= ss->rate*1.05) + _ss.rate = ss->rate; + if (require_exact_channel_number) { if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) { pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); @@ -303,7 +307,7 @@ int pa_alsa_set_hw_params( if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { - pa_log_debug("Set buffer size first, period size second."); + pa_log_debug("Set buffer size first (to %lu samples), period size second (to %lu samples).", (unsigned long) _buffer_size, (unsigned long) _period_size); goto success; } @@ -311,7 +315,7 @@ int pa_alsa_set_hw_params( if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { - pa_log_debug("Set period size first, buffer size second."); + pa_log_debug("Set period size first (to %lu samples), buffer size second (to %lu samples).", (unsigned long) _period_size, (unsigned long) _buffer_size); goto success; } } @@ -322,7 +326,7 @@ int pa_alsa_set_hw_params( /* Third try: set only buffer size */ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { - pa_log_debug("Set only buffer size second."); + pa_log_debug("Set only buffer size (to %lu samples).", (unsigned long) _buffer_size); goto success; } } @@ -333,7 +337,7 @@ int pa_alsa_set_hw_params( /* Fourth try: set only period size */ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { - pa_log_debug("Set only period size second."); + pa_log_debug("Set only period size (to %lu samples).", (unsigned long) _period_size); goto success; } } @@ -374,9 +378,7 @@ success: goto finish; } - /* If the sample rate deviates too much, we need to resample */ - if (_ss.rate < ss->rate*.95 || _ss.rate > ss->rate*1.05) - ss->rate = _ss.rate; + ss->rate = _ss.rate; ss->channels = _ss.channels; ss->format = _ss.format; diff --git a/src/modules/module-device-manager-symdef.h b/src/modules/module-device-manager-symdef.h new file mode 100644 index 0000000..70edb51 --- /dev/null +++ b/src/modules/module-device-manager-symdef.h @@ -0,0 +1,29 @@ +#ifndef foomoduledevicemanagersymdeffoo +#define foomoduledevicemanagersymdeffoo + +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/macro.h> + +#define pa__init module_device_manager_LTX_pa__init +#define pa__done module_device_manager_LTX_pa__done +#define pa__get_author module_device_manager_LTX_pa__get_author +#define pa__get_description module_device_manager_LTX_pa__get_description +#define pa__get_usage module_device_manager_LTX_pa__get_usage +#define pa__get_version module_device_manager_LTX_pa__get_version +#define pa__get_deprecated module_device_manager_LTX_pa__get_deprecated +#define pa__load_once module_device_manager_LTX_pa__load_once +#define pa__get_n_used module_device_manager_LTX_pa__get_n_used + +int pa__init(pa_module*m); +void pa__done(pa_module*m); +int pa__get_n_used(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); +const char* pa__get_deprecated(void); +pa_bool_t pa__load_once(void); + +#endif diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c new file mode 100644 index 0000000..3991043 --- /dev/null +++ b/src/modules/module-device-manager.c @@ -0,0 +1,1540 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio 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. + + PulseAudio 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 PulseAudio; 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 <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/rtclock.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-native.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/database.h> + +#include "module-device-manager-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "do_routing=<Automatically route streams based on a priority list (unique per-role)?> " + "on_hotplug=<When new device becomes available, recheck streams?> " + "on_rescue=<When device becomes unavailable, recheck streams?>"); + +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) +#define DUMP_DATABASE + +static const char* const valid_modargs[] = { + "do_routing", + "on_hotplug", + "on_rescue", + NULL +}; + +#define NUM_ROLES 9 +enum { + ROLE_NONE, + ROLE_VIDEO, + ROLE_MUSIC, + ROLE_GAME, + ROLE_EVENT, + ROLE_PHONE, + ROLE_ANIMATION, + ROLE_PRODUCTION, + ROLE_A11Y, +}; + +typedef uint32_t role_indexes_t[NUM_ROLES]; + +static const char* role_names[NUM_ROLES] = { + "none", + "video", + "music", + "game", + "event", + "phone", + "animation", + "production", + "a11y", +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_subscription *subscription; + pa_hook_slot + *sink_new_hook_slot, + *source_new_hook_slot, + *sink_input_new_hook_slot, + *source_output_new_hook_slot, + *sink_put_hook_slot, + *source_put_hook_slot, + *sink_unlink_hook_slot, + *source_unlink_hook_slot, + *connection_unlink_hook_slot; + pa_time_event *save_time_event; + pa_database *database; + + pa_native_protocol *protocol; + pa_idxset *subscribed; + + pa_bool_t on_hotplug; + pa_bool_t on_rescue; + pa_bool_t do_routing; + + role_indexes_t preferred_sinks; + role_indexes_t preferred_sources; +}; + +#define ENTRY_VERSION 1 + +struct entry { + uint8_t version; + char description[PA_NAME_MAX]; + pa_bool_t user_set_description; + char icon[PA_NAME_MAX]; + role_indexes_t priority; +} PA_GCC_PACKED; + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + + +static struct entry* read_entry(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e; + + pa_assert(u); + pa_assert(name); + + key.data = (char*) name; + key.size = strlen(name); + + pa_zero(data); + + if (!pa_database_get(u->database, &key, &data)) + goto fail; + + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); + goto fail; + } + + e = (struct entry*) data.data; + + if (e->version != ENTRY_VERSION) { + pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); + goto fail; + } + + if (!memchr(e->description, 0, sizeof(e->description))) { + pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name); + goto fail; + } + + if (!memchr(e->icon, 0, sizeof(e->icon))) { + pa_log_warn("Database contains entry for device %s with missing NUL byte in icon", name); + goto fail; + } + + return e; + +fail: + + pa_datum_free(&data); + return NULL; +} + +#ifdef DUMP_DATABASE +static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) { + pa_assert(u); + pa_assert(human); + + if (sink_mode) { + pa_sink *s; + if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No sink specified", human); + } else { + pa_source *s; + if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No source specified", human); + } +} + +static void dump_database(struct userdata *u) { + pa_datum key; + pa_bool_t done; + + pa_assert(u); + + done = !pa_database_first(u->database, &key, NULL); + + pa_log_debug("Dumping database"); + while (!done) { + char *name; + struct entry *e; + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + + if ((e = read_entry(u, name))) { + pa_log_debug(" Got entry: %s", name); + pa_log_debug(" Description: %s", e->description); + pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u", + e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]); + pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u", + e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]); + pa_xfree(e); + } + + pa_xfree(name); + + pa_datum_free(&key); + key = next_key; + } + + if (u->do_routing) { + pa_log_debug(" Highest priority devices per-role:"); + + pa_log_debug(" Sinks:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, TRUE); + } + + pa_log_debug(" Sources:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, FALSE); + } + } + + pa_log_debug("Completed database dump"); +} +#endif + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(u); + + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + pa_database_sync(u->database); + pa_log_info("Synced."); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif +} + +static void notify_subscribers(struct userdata *u) { + + pa_native_connection *c; + uint32_t idx; + + pa_assert(u); + + for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) { + pa_tagstruct *t; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION); + pa_tagstruct_putu32(t, 0); + pa_tagstruct_putu32(t, u->module->index); + pa_tagstruct_puts(t, u->module->name); + pa_tagstruct_putu32(t, SUBCOMMAND_EVENT); + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t); + } +} + +static void trigger_save(struct userdata *u) { + + pa_assert(u); + + notify_subscribers(u); + + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { + + pa_assert(a); + pa_assert(b); + + if (strncmp(a->description, b->description, sizeof(a->description)) + || a->user_set_description != b->user_set_description + || strncmp(a->icon, b->icon, sizeof(a->icon))) + return FALSE; + + for (int i=0; i < NUM_ROLES; ++i) + if (a->priority[i] != b->priority[i]) + return FALSE; + + return TRUE; +} + +static char *get_name(const char *key, const char *prefix) { + char *t; + + if (strncmp(key, prefix, strlen(prefix))) + return NULL; + + t = pa_xstrdup(key + strlen(prefix)); + return t; +} + +static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) { + struct entry *old; + + pa_assert(u); + pa_assert(entry); + pa_assert(name); + pa_assert(prefix); + + if ((old = read_entry(u, name))) + *entry = *old; + else { + /* This is a new device, so make sure we write it's priority list correctly */ + role_indexes_t max_priority; + pa_datum key; + pa_bool_t done; + + pa_zero(max_priority); + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we calculate the current max priority for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name2; + struct entry *e; + + name2 = pa_xstrndup(key.data, key.size); + + if ((e = read_entry(u, name2))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + max_priority[i] = PA_MAX(max_priority[i], e->priority[i]); + } + + pa_xfree(e); + } + + pa_xfree(name2); + } + pa_datum_free(&key); + key = next_key; + } + + /* Actually initialise our entry now we've calculated it */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + entry->priority[i] = max_priority[i] + 1; + } + entry->user_set_description = FALSE; + } + + return old; +} + +static uint32_t get_role_index(const char* role) { + pa_assert(role); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) + if (strcmp(role, role_names[i]) == 0) + return i; + + return PA_INVALID_INDEX; +} + +static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) { + role_indexes_t *indexes, highest_priority_available; + pa_datum key; + pa_bool_t done, sink_mode; + + pa_assert(u); + pa_assert(prefix); + + sink_mode = (strcmp(prefix, "sink:") == 0); + + if (sink_mode) + indexes = &u->preferred_sinks; + else + indexes = &u->preferred_sources; + + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + (*indexes)[i] = PA_INVALID_INDEX; + } + pa_zero(highest_priority_available); + + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we find the highest priority device for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name, *device_name; + struct entry *e; + + name = pa_xstrndup(key.data, key.size); + device_name = get_name(name, prefix); + + if ((e = read_entry(u, name))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) { + /* We've found a device with a higher priority than that we've currently got, + so see if it is currently available or not and update our list */ + uint32_t idx; + pa_bool_t found = FALSE; + + if (sink_mode) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if ((pa_sink*) ignore_device == sink) + continue; + if (strcmp(sink->name, device_name) == 0) { + found = TRUE; + idx = sink->index; /* Is this needed? */ + break; + } + } + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + if ((pa_source*) ignore_device == source) + continue; + if (strcmp(source->name, device_name) == 0) { + found = TRUE; + idx = source->index; /* Is this needed? */ + break; + } + } + } + if (found) { + highest_priority_available[i] = e->priority[i]; + (*indexes)[i] = idx; + } + + } + } + + pa_xfree(e); + } + + pa_xfree(name); + pa_xfree(device_name); + } + + pa_datum_free(&key); + key = next_key; + } +} + + +static void route_sink_input(struct userdata *u, pa_sink_input *si) { + const char *role; + uint32_t role_index, device_index; + pa_sink *sink; + + pa_assert(u); + pa_assert(u->do_routing); + + if (si->save_sink) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!si->sink) + return; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + return; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) + return; + + if (si->sink != sink) + pa_sink_input_move_to(si, sink, FALSE); +} + +static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "sink:", ignore_sink); + + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { + route_sink_input(u, si); + } + + return PA_HOOK_OK; +} + +static void route_source_output(struct userdata *u, pa_source_output *so) { + const char *role; + uint32_t role_index, device_index; + pa_source *source; + + pa_assert(u); + pa_assert(u->do_routing); + + if (so->save_source) + return; + + if (so->direct_on_input) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + return; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + return; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) + return; + + if (so->source != source) + pa_source_output_move_to(so, source, FALSE); +} + +static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) { + pa_source_output *so; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "source:", ignore_source); + + PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { + route_source_output(u, so); + } + + return PA_HOOK_OK; +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + struct entry entry, *old = NULL; + char *name = NULL; + pa_datum key, data; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) && + + /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + pa_zero(entry); + entry.version = ENTRY_VERSION; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + pa_sink_input *si; + + if (!u->do_routing) + return; + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_sink_input(u, si); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { + pa_source_output *so; + + if (!u->do_routing) + return; + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_source_output(u, so); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + pa_sink *sink; + + if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) + return; + + name = pa_sprintf_malloc("sink:%s", sink->name); + + old = load_or_initialize_entry(u, &entry, name, "sink:"); + + if (!entry.user_set_description) + pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_sink_set_description(sink, entry.description); + } + + pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + pa_source *source; + + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (!(source = pa_idxset_get_by_index(c->sources, idx))) + return; + + if (source->monitor_of) + return; + + name = pa_sprintf_malloc("source:%s", source->name); + + old = load_or_initialize_entry(u, &entry, name, "source:"); + + if (!entry.user_set_description) + pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_source_set_description(source, entry.description); + } + + pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + } + + pa_assert(name); + + if (old) { + + if (entries_equal(old, &entry)) { + pa_xfree(old); + pa_xfree(name); + + return; + } + + pa_xfree(old); + } + + key.data = name; + key.size = strlen(name); + + data.data = &entry; + data.size = sizeof(entry); + + pa_log_info("Storing device %s.", name); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) + trigger_save(u); + else + pa_log_warn("Could not save device");; + + pa_xfree(name); +} + +static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("sink:%s", new_data->name); + + if ((e = read_entry(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + pa_log_info("Restoring description for sink %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = read_entry(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */ + pa_log_info("Restoring description for source %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->sink) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_sink *sink; + + if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) { + new_data->sink = sink; + new_data->save_sink = FALSE; + } + } + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->direct_on_input) + return PA_HOOK_OK; + + if (new_data->source) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_source *source; + + if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) { + new_data->source = source; + new_data->save_source = FALSE; + } + } + } + } + + return PA_HOOK_OK; +} + + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_sink_inputs(u, NULL); +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_source_outputs(u, NULL); +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_sink_inputs(u, sink); +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_source_outputs(u, source); +} + + +static void apply_entry(struct userdata *u, const char *name, struct entry *e) { + uint32_t idx; + char *n; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + if (!e->user_set_description) + return; + + if ((n = get_name(name, "sink:"))) { + pa_sink *s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + pa_log_info("Setting description for sink %s to '%s'", s->name, e->description); + pa_sink_set_description(s, e->description); + } + pa_xfree(n); + } + else if ((n = get_name(name, "source:"))) { + pa_source *s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + if (s->monitor_of) { + pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name); + continue; + } + + pa_log_info("Setting description for source %s to '%s'", s->name, e->description); + pa_source_set_description(s, e->description); + } + pa_xfree(n); + } +} + + +#define EXT_VERSION 1 + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + + case SUBCOMMAND_READ: { + pa_datum key; + pa_bool_t done; + + if (!pa_tagstruct_eof(t)) + goto fail; + + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; + struct entry *e; + char *name; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); + + if ((e = read_entry(u, name))) { + uint32_t idx; + char *devname; + uint32_t found_index = PA_INVALID_INDEX; + + if ((devname = get_name(name, "sink:"))) { + pa_sink* s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (strcmp(s->name, devname) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(devname); + } else if ((devname = get_name(name, "source:"))) { + pa_source* s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (strcmp(s->name, devname) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(devname); + } + + pa_tagstruct_puts(reply, name); + pa_tagstruct_puts(reply, e->description); + pa_tagstruct_puts(reply, e->icon); + pa_tagstruct_putu32(reply, found_index); + pa_tagstruct_putu32(reply, NUM_ROLES); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) { + pa_tagstruct_puts(reply, role_names[i]); + pa_tagstruct_putu32(reply, e->priority[i]); + } + + pa_xfree(e); + } + + pa_xfree(name); + + key = next_key; + } + + break; + } + + case SUBCOMMAND_RENAME: { + + struct entry *e; + const char *device, *description; + + if (pa_tagstruct_gets(t, &device) < 0 || + pa_tagstruct_gets(t, &description) < 0) + goto fail; + + if (!device || !*device || !description || !*description) + goto fail; + + if ((e = read_entry(u, device))) { + pa_datum key, data; + + pa_strlcpy(e->description, description, sizeof(e->description)); + e->user_set_description = TRUE; + + key.data = (char *) device; + key.size = strlen(device); + + data.data = e; + data.size = sizeof(*e); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) { + apply_entry(u, device, e); + + trigger_save(u); + } + else + pa_log_warn("Could not save device"); + + pa_xfree(e); + } + else + pa_log_warn("Could not rename device %s, no entry in database", device); + + break; + } + + case SUBCOMMAND_DELETE: + + while (!pa_tagstruct_eof(t)) { + const char *name; + pa_datum key; + + if (pa_tagstruct_gets(t, &name) < 0) + goto fail; + + key.data = (char*) name; + key.size = strlen(name); + + /** @todo: Reindex the priorities */ + pa_database_unset(u->database, &key); + } + + trigger_save(u); + + break; + + case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: { + + pa_bool_t enable; + + if (pa_tagstruct_get_boolean(t, &enable) < 0) + goto fail; + + if ((u->do_routing = enable)) { + /* Update our caches */ + update_highest_priority_device_indexes(u, "sink:", NULL); + update_highest_priority_device_indexes(u, "source:", NULL); + } + + break; + } + + case SUBCOMMAND_REORDER: { + + const char *role; + struct entry *e; + uint32_t role_index, n_devices; + pa_datum key, data; + pa_bool_t done, sink_mode = TRUE; + struct device_t { uint32_t prio; char *device; }; + struct device_t *device; + struct device_t **devices; + uint32_t i, idx, offset; + pa_hashmap *h; + /*void *state;*/ + pa_bool_t first; + + if (pa_tagstruct_gets(t, &role) < 0 || + pa_tagstruct_getu32(t, &n_devices) < 0 || + n_devices < 1) + goto fail; + + if (PA_INVALID_INDEX == (role_index = get_role_index(role))) + goto fail; + + /* Cycle through the devices given and make sure they exist */ + h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + first = TRUE; + idx = 0; + for (i = 0; i < n_devices; ++i) { + const char *s; + if (pa_tagstruct_gets(t, &s) < 0) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Protocol error on reorder"); + goto fail; + } + + /* Ensure this is a valid entry */ + if (!(e = read_entry(u, s))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Client specified an unknown device in it's reorder list."); + goto fail; + } + pa_xfree(e); + + if (first) { + first = FALSE; + sink_mode = (0 == strncmp("sink:", s, 5)); + } else if ((sink_mode && 0 != strncmp("sink:", s, 5)) + || (!sink_mode && 0 != strncmp("source:", s, 7))) + { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Attempted to reorder mixed devices (sinks and sources)"); + goto fail; + } + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrdup(s); + if (pa_hashmap_put(h, device->device, device) == 0) { + device->prio = idx; + idx++; + } else { + pa_xfree(device->device); + pa_xfree(device); + } + } + + /*pa_log_debug("Hashmap contents (received from client)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now cycle through our list and add all the devices. + This has the effect of addign in any in our DB, + not specified in the device list (and thus will be + tacked on at the end) */ + offset = idx; + done = !pa_database_first(u->database, &key, NULL); + + while (!done && idx < 256) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrndup(key.data, key.size); + if ((sink_mode && 0 == strncmp("sink:", device->device, 5)) + || (!sink_mode && 0 == strncmp("source:", device->device, 7))) { + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + if (pa_hashmap_put(h, device->device, device) == 0 + && (e = read_entry(u, device->device))) { + /* We add offset on to the existing priorirty so that when we order, the + existing entries are always lower priority than the new ones. */ + device->prio = (offset + e->priority[role_index]); + pa_xfree(e); + } + else { + pa_xfree(device->device); + pa_xfree(device); + } + } else { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_datum_free(&key); + + key = next_key; + } + + /*pa_log_debug("Hashmap contents (combined with database)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now we put all the entries in a simple list for sorting it. */ + n_devices = pa_hashmap_size(h); + devices = pa_xnew(struct device_t *, n_devices); + idx = 0; + while ((device = pa_hashmap_steal_first(h))) { + devices[idx++] = device; + } + pa_hashmap_free(h, NULL, NULL); + + /* Simple bubble sort */ + for (i = 0; i < n_devices; ++i) { + for (uint32_t j = i; j < n_devices; ++j) { + if (devices[i]->prio > devices[j]->prio) { + struct device_t *tmp; + tmp = devices[i]; + devices[i] = devices[j]; + devices[j] = tmp; + } + } + } + + /*pa_log_debug("Sorted device list"); + for (i = 0; i < n_devices; ++i) { + pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio); + }*/ + + /* Go through in order and write the new entry and cleanup our own list */ + idx = 1; + first = TRUE; + for (i = 0; i < n_devices; ++i) { + if ((e = read_entry(u, devices[i]->device))) { + if (e->priority[role_index] == idx) + idx++; + else { + e->priority[role_index] = idx; + + key.data = (char *) devices[i]->device; + key.size = strlen(devices[i]->device); + + data.data = e; + data.size = sizeof(*e); + + if (pa_database_set(u->database, &key, &data, TRUE) == 0) { + first = FALSE; + idx++; + } + } + + pa_xfree(e); + } + pa_xfree(devices[i]->device); + pa_xfree(devices[i]); + } + + if (!first) { + trigger_save(u); + + if (sink_mode) + route_sink_inputs(u, NULL); + else + route_source_outputs(u, NULL); + } + + break; + } + + case SUBCOMMAND_SUBSCRIBE: { + + pa_bool_t enabled; + + if (pa_tagstruct_get_boolean(t, &enabled) < 0 || + !pa_tagstruct_eof(t)) + goto fail; + + if (enabled) + pa_idxset_put(u->subscribed, c, NULL); + else + pa_idxset_remove_by_data(u->subscribed, c, NULL); + + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + + fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) { + pa_assert(p); + pa_assert(c); + pa_assert(u); + + pa_idxset_remove_by_data(u->subscribed, c, NULL); + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + char *fname; + pa_sink *sink; + pa_source *source; + uint32_t idx; + pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "do_routing", &do_routing) < 0 || + pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("on_hotplug= and on_rescue= expect boolean arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->do_routing = do_routing; + u->on_hotplug = on_hotplug; + u->on_rescue = on_rescue; + u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + u->connection_unlink_hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u); + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); + + /* Used to handle device description management */ + u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u); + u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u); + + /* The following slots are used to deal with routing */ + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) source_output_new_hook_callback, u); + + if (on_hotplug) { + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) source_put_hook_callback, u); + } + + if (on_rescue) { + /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */ + u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u); + } + + if (!(fname = pa_state_path("device-manager", TRUE))) + goto fail; + + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); + pa_xfree(fname); + goto fail; + } + + pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_xfree(fname); + + /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */ + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + PA_IDXSET_FOREACH(source, m->core->sources, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + + /* Perform the routing (if it's enabled) which will update our priority list cache too */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX; + } + + route_sink_inputs(u, NULL); + route_source_outputs(u, NULL); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_new_hook_slot) + pa_hook_slot_free(u->sink_new_hook_slot); + if (u->source_new_hook_slot) + pa_hook_slot_free(u->source_new_hook_slot); + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); + + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->source_put_hook_slot) + pa_hook_slot_free(u->source_put_hook_slot); + + if (u->sink_unlink_hook_slot) + pa_hook_slot_free(u->sink_unlink_hook_slot); + if (u->source_unlink_hook_slot) + pa_hook_slot_free(u->source_unlink_hook_slot); + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + if (u->database) + pa_database_close(u->database); + + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + if (u->subscribed) + pa_idxset_free(u->subscribed, NULL, NULL); + + pa_xfree(u); +} diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c index 4c02e95..82bcd14 100644 --- a/src/modules/module-rygel-media-server.c +++ b/src/modules/module-rygel-media-server.c @@ -464,8 +464,18 @@ static char **child_array(struct userdata *u, const char *path, unsigned *n) { if (pa_streq(path, OBJECT_SINKS)) m = pa_idxset_size(u->core->sinks); - else + else { + unsigned k; + m = pa_idxset_size(u->core->sources); + k = pa_idxset_size(u->core->sinks); + + pa_assert(m >= k); + + /* Subtract the monitor sources from the numbers of + * sources. There is one monitor source for each sink */ + m -= k; + } array = pa_xnew(char*, m); *n = 0; @@ -473,14 +483,20 @@ static char **child_array(struct userdata *u, const char *path, unsigned *n) { if (pa_streq(path, OBJECT_SINKS)) { pa_sink *sink; - PA_IDXSET_FOREACH(sink, u->core->sinks, idx) + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + pa_assert((*n) < m); array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); + } } else { pa_source *source; - PA_IDXSET_FOREACH(source, u->core->sources, idx) - if (!source->monitor_of) + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + + if (!source->monitor_of) { + pa_assert((*n) < m); array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); + } + } } pa_assert((*n) <= m); @@ -529,16 +545,20 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag free_child_array(array, n); } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + unsigned n, k; + + n = pa_idxset_size(u->core->sinks); + k = pa_idxset_size(u->core->sources); + pa_assert(k >= n); + pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_unsigned(r, NULL, - pa_streq(path, OBJECT_SINKS) ? - pa_idxset_size(u->core->sinks) : - pa_idxset_size(u->core->sources)); + pa_streq(path, OBJECT_SINKS) ? n : k - n); } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { DBusMessageIter iter, sub; char **array; - unsigned n; + unsigned n, k; pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); @@ -550,10 +570,13 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag array = child_array(u, path, &n); append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); free_child_array(array, n); + + n = pa_idxset_size(u->core->sinks); + k = pa_idxset_size(u->core->sources); + pa_assert(k >= n); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", - pa_streq(path, OBJECT_SINKS) ? - pa_idxset_size(u->core->sinks) : - pa_idxset_size(u->core->sources)); + pa_streq(path, OBJECT_SINKS) ? n : k - n); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); diff --git a/src/pulse/context.c b/src/pulse/context.c index 23ae30c..7468d0a 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -128,6 +128,9 @@ static void reset_callbacks(pa_context *c) { c->event_callback = NULL; c->event_userdata = NULL; + c->ext_device_manager.callback = NULL; + c->ext_device_manager.userdata = NULL; + c->ext_stream_restore.callback = NULL; c->ext_stream_restore.userdata = NULL; } @@ -1434,6 +1437,8 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t if (!strcmp(name, "module-stream-restore")) pa_ext_stream_restore_command(c, tag, t); + else if (!strcmp(name, "module-device-manager")) + pa_ext_device_manager_command(c, tag, t); else pa_log(_("Received message for unknown extension '%s'"), name); diff --git a/src/pulse/ext-device-manager.c b/src/pulse/ext-device-manager.c new file mode 100644 index 0000000..57cb57c --- /dev/null +++ b/src/pulse/ext-device-manager.c @@ -0,0 +1,437 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio 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. + + PulseAudio 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 PulseAudio; 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 <pulse/context.h> +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "operation.h" +#include "fork-detect.h" + +#include "ext-device-manager.h" + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + +static void ext_device_manager_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_device_manager_test_cb_t cb = (pa_ext_device_manager_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +static void ext_device_manager_read_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_ext_device_manager_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_gets(t, &i.icon) < 0 || + pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_getu32(t, &i.n_role_priorities) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (i.n_role_priorities > 0) { + uint32_t j; + i.role_priorities = pa_xnew0(pa_ext_device_manager_role_priority_info, i.n_role_priorities+1); + + for (j = 0; j < i.n_role_priorities; j++) { + + if (pa_tagstruct_gets(t, &i.role_priorities[j].role) < 0 || + pa_tagstruct_getu32(t, &i.role_priorities[j].priority) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.role_priorities); + goto finish; + } + } + + /* Terminate with an extra NULL entry, just to make sure */ + i.role_priorities[j].role = NULL; + i.role_priorities[j].priority = 0; + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_xfree(i.role_priorities); + } + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_read_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(device); + pa_assert(description); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_RENAME); + + pa_tagstruct_puts(t, device); + pa_tagstruct_puts(t, description); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + const char *const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(s); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_DELETE); + + for (k = s; *k; k++) { + if (!*k || !**k) + goto fail; + + pa_tagstruct_puts(t, *k); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +fail: + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + if (t) + pa_tagstruct_free(t); + + pa_context_set_error(c, PA_ERR_INVALID); + return NULL; +} + +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING); + pa_tagstruct_put_boolean(t, !!enable); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag, i; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + pa_assert(role); + pa_assert(devices); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_REORDER); + pa_tagstruct_puts(t, role); + + i = 0; while (devices[i]) i++; + pa_tagstruct_putu32(t, i); + + i = 0; + while (devices[i]) + pa_tagstruct_puts(t, devices[i++]); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE); + pa_tagstruct_put_boolean(t, enable); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + c->ext_device_manager.callback = cb; + c->ext_device_manager.userdata = userdata; +} + +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t) { + uint32_t subcommand; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &subcommand) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (subcommand != SUBCOMMAND_EVENT) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (c->ext_device_manager.callback) + c->ext_device_manager.callback(c, c->ext_device_manager.userdata); +} diff --git a/src/pulse/ext-device-manager.h b/src/pulse/ext-device-manager.h new file mode 100644 index 0000000..df0ab92 --- /dev/null +++ b/src/pulse/ext-device-manager.h @@ -0,0 +1,128 @@ +#ifndef foopulseextdevicemanagerhfoo +#define foopulseextdevicemanagerhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio 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. + + PulseAudio 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 PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/context.h> +#include <pulse/version.h> + +/** \file + * + * Routines for controlling module-device-manager + */ + +PA_C_DECL_BEGIN + +typedef struct pa_ext_device_manager_role_priority_info { + const char *role; + uint32_t priority; +} pa_ext_device_manager_role_priority_info; + +/** Stores information about one device in the device database that is + * maintained by module-device-manager. \since 0.9.21 */ +typedef struct pa_ext_device_manager_info { + const char *name; /**< Identifier string of the device. A string like "sink:" or similar followed by the name of the device. */ + const char *description; /**< The description of the device when it was last seen, if applicable and saved */ + const char *icon; /**< The icon given to the device */ + uint32_t index; /**< The device index if it is currently available or PA_INVALID_INDEX */ + uint32_t n_role_priorities; /**< How many role priorities do we have? */ + pa_ext_device_manager_role_priority_info *role_priorities; /**< An array of role priority structures or NULL */ +} pa_ext_device_manager_info; + +/** Callback prototype for pa_ext_device_manager_test(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_read(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_read_cb_t)( + pa_context *c, + const pa_ext_device_manager_info *info, + int eol, + void *userdata); + +/** Read all entries from the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata); + +/** Sets the description for a device. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata); + +/** Delete entries from the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata); + +/** Enable the role-based device-priority routing mode. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Prefer a given device in the priority list. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata); + +/** Subscribe to changes in the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_set_subscribe_cb(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_subscribe_cb_t)( + pa_context *c, + void *userdata); + +/** Set the subscription callback that is called when + * pa_ext_device_manager_subscribe() was called. \since 0.9.21 */ +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/internal.h b/src/pulse/internal.h index e069c9e..b371bfc 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -28,6 +28,7 @@ #include <pulse/stream.h> #include <pulse/operation.h> #include <pulse/subscribe.h> +#include <pulse/ext-device-manager.h> #include <pulse/ext-stream-restore.h> #include <pulsecore/socket-client.h> @@ -102,6 +103,10 @@ struct pa_context { /* Extension specific data */ struct { + pa_ext_device_manager_subscribe_cb_t callback; + void *userdata; + } ext_device_manager; + struct { pa_ext_stream_restore_subscribe_cb_t callback; void *userdata; } ext_stream_restore; @@ -283,6 +288,7 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta #define PA_FAIL_RETURN_NULL(context, error) \ PA_FAIL_RETURN_ANY(context, error, NULL) +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t); void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m); diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 2bc2b1e..d01985b 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -387,9 +387,26 @@ static void check_smoother_status(pa_stream *s, pa_bool_t aposteriori, pa_bool_t if (s->suspended || s->corked || force_stop) pa_smoother_pause(s->smoother, x); - else if (force_start || s->buffer_attr.prebuf == 0) - pa_smoother_resume(s->smoother, x, TRUE); + else if (force_start || s->buffer_attr.prebuf == 0) { + + if (!s->timing_info_valid && + !aposteriori && + !force_start && + !force_stop && + s->context->version >= 13) { + + /* If the server supports STARTED events we take them as + * indications when audio really starts/stops playing, if + * we don't have any timing info yet -- instead of trying + * to be smart and guessing the server time. Otherwise the + * unknown transport delay add too much noise to our time + * calculations. */ + + return; + } + pa_smoother_resume(s->smoother, x, TRUE); + } /* Please note that we have no idea if playback actually started * if prebuf is non-zero! */ @@ -1457,6 +1474,11 @@ pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *us PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, TRUE); + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); t = pa_tagstruct_command(s->context, PA_COMMAND_DRAIN_PLAYBACK_STREAM, &tag); @@ -1464,6 +1486,10 @@ pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *us pa_pstream_send_tagstruct(s->context->pstream, t); pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + /* This might cause the read index to conitnue again, hence + * let's request a timing update */ + request_auto_timing_update(s, TRUE); + return o; } @@ -2012,6 +2038,11 @@ pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, voi PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, TRUE); + s->corked = b; o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); @@ -2027,8 +2058,8 @@ pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, voi check_smoother_status(s, FALSE, FALSE, FALSE); - /* This might cause the indexes to hang/start again, hence - * let's request a timing update */ + /* This might cause the indexes to hang/start again, hence let's + * request a timing update, after the cork/uncork, too */ request_auto_timing_update(s, TRUE); return o; @@ -2065,6 +2096,11 @@ pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *use PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + /* Ask for a timing update *before* the flush, so that the + * transport usec is as up to date as possible when we get the + * underflow message and update the smoother status*/ + request_auto_timing_update(s, TRUE); + if (!(o = stream_send_simple_command(s, (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM), cb, userdata))) return NULL; @@ -2099,6 +2135,11 @@ pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *us PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, TRUE); + if (!(o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata))) return NULL; @@ -2120,6 +2161,11 @@ pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *u PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, TRUE); + if (!(o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata))) return NULL; @@ -2371,6 +2417,11 @@ pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, TRUE); + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); t = pa_tagstruct_command( diff --git a/src/pulse/version.h b/src/pulse/version.h index 55851ef..99e1d43 100644 --- a/src/pulse/version.h +++ b/src/pulse/version.h @@ -35,7 +35,7 @@ PA_C_DECL_BEGIN /** Return the version of the header files. Keep in mind that this is a macro and not a function, so it is impossible to get the pointer of it. */ -#define pa_get_headers_version() ("0.9.20") +#define pa_get_headers_version() ("0.9.21") /** Return the version of the library the current application is * linked to. */ @@ -58,7 +58,7 @@ const char* pa_get_library_version(void); #define PA_MINOR 9 /** The micro version of PA. \since 0.9.15 */ -#define PA_MICRO 20 +#define PA_MICRO 21 /** Evaluates to TRUE if the PulseAudio library version is equal or * newer than the specified. \since 0.9.16 */ |