Suspend or resume the specified sink (which may be
+- specified either by its name or index), depending whether true
++ specified either by its symbolic name or numerical index), depending whether true
+ (suspend) or false (resume) is passed as last argument. Suspending
+ a sink will pause all playback. Depending on the module implementing
+ the sink this might have the effect that the underlying device is
+@@ -147,7 +147,7 @@ License along with PulseAudio; if not, see .
+
+
++
++ send-message RECIPIENT MESSAGE MESSAGE_PARAMETERS
++ Send a message to the specified recipient object. If applicable an additional string containing
++ message parameters can be specified. A string is returned as a response to the message. For available messages
++ see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.
++
++
+
+ subscribe
+ Subscribe to events, pactl does not exit by itself, but keeps waiting for new events.
+diff --git a/man/pulse-cli-syntax.5.xml.in b/man/pulse-cli-syntax.5.xml.in
+index a8d50d97f..5bc94b9c9 100644
+--- a/man/pulse-cli-syntax.5.xml.in
++++ b/man/pulse-cli-syntax.5.xml.in
+@@ -306,6 +306,13 @@ License along with PulseAudio; if not, see .
+ Debug: Show shared properties.
+
+
++
++ send-message recipient message message_parameters
++ Send a message to the specified recipient object. If applicable an additional string containing
++ message parameters can be specified. A string is returned as a response to the message. For available messages
++ see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.
++
++
+
+ exit
+ Terminate the daemon. If you want to terminate a CLI
+diff --git a/meson.build b/meson.build
+index 658eeee57..23e04ef46 100644
+--- a/meson.build
++++ b/meson.build
+@@ -19,7 +19,7 @@ endif
+ pa_version_major_minor = pa_version_major + '.' + pa_version_minor
+
+ pa_api_version = 12
+-pa_protocol_version = 34
++pa_protocol_version = 35
+
+ # The stable ABI for client applications, for the version info x:y:z
+ # always will hold x=z
+@@ -125,7 +125,7 @@ cdata = configuration_data()
+ cdata.set_quoted('PACKAGE', 'pulseaudio')
+ cdata.set_quoted('PACKAGE_NAME', 'pulseaudio')
+ cdata.set_quoted('PACKAGE_VERSION', pa_version_str)
+-cdata.set_quoted('CANONICAL_HOST', host_machine.cpu())
++cdata.set_quoted('CANONICAL_HOST', target_machine.cpu_family())
+ cdata.set('PA_MAJOR', pa_version_major)
+ cdata.set('PA_MINOR', pa_version_minor)
+ cdata.set('PA_API_VERSION', pa_api_version)
+@@ -136,6 +136,7 @@ cdata.set_quoted('PA_SRCDIR', join_paths(meson.current_source_dir(), 'src'))
+ cdata.set_quoted('PA_BUILDDIR', meson.current_build_dir())
+ cdata.set_quoted('PA_SOEXT', '.so')
+ cdata.set_quoted('PA_DEFAULT_CONFIG_DIR', pulsesysconfdir)
++cdata.set('PA_DEFAULT_CONFIG_DIR_UNQUOTED', pulsesysconfdir)
+ cdata.set_quoted('PA_BINARY', join_paths(bindir, 'pulseaudio'))
+ cdata.set_quoted('PA_SYSTEM_RUNTIME_PATH', join_paths(localstatedir, 'run', 'pulse'))
+ cdata.set_quoted('PA_SYSTEM_CONFIG_PATH', join_paths(localstatedir, 'lib', 'pulse'))
+@@ -157,6 +158,10 @@ cdata.set('top_srcdir', meson.source_root())
+ # First some defaults to keep config file generation happy
+ cdata.set('HAVE_COREAUDIO', 0)
+ cdata.set('HAVE_WAVEOUT', 0)
++
++platform_socket_dep = []
++platform_dep = []
++
+ # FIXME: This was not tested. Maybe some flags should better be CFLAGS,
+ # rather than ending up in the config.h file?
+ if host_machine.system() == 'darwin'
+@@ -164,7 +169,20 @@ if host_machine.system() == 'darwin'
+ cdata.set('_DARWIN_C_SOURCE', '200112L') # Needed to get NSIG on Mac OS
+ elif host_machine.system() == 'windows'
+ cdata.set('OS_IS_WIN32', 1)
++ cdata.set('HAVE_WINDOWS_H', 1)
++ cdata.set('HAVE_WAVEOUT', 1)
++ cdata.set('HAVE_WINSOCK2_H', 1)
++ cdata.set('HAVE_WS2TCPIP_H', 1)
+ cdata.set('WIN32_LEAN_AND_MEAN', 1) # Needed to avoid including unnecessary headers on Windows
++ cdata.set('gid_t', 'int')
++ cdata.set('uid_t', 'int')
++ ws2_32_dep = meson.get_compiler('c').find_library('ws2_32')
++ winsock_dep = meson.get_compiler('c').find_library('wsock32')
++ ole32_dep = meson.get_compiler('c').find_library('ole32')
++ ssp_dep = meson.get_compiler('c').find_library('ssp')
++ pcreposix_dep = meson.get_compiler('c').find_library('pcreposix')
++ platform_socket_dep = [ws2_32_dep, winsock_dep]
++ platform_dep = [ole32_dep, ssp_dep, pcreposix_dep]
+ #elif host_machine.system() == 'solaris'
+ # # Apparently meson has no solaris support?
+ # # Needed to get declarations for msg_control and msg_controllen on Solaris
+@@ -220,7 +238,6 @@ check_headers = [
+ 'sys/un.h',
+ 'sys/wait.h',
+ 'syslog.h',
+- 'valgrind/memcheck.h',
+ 'xlocale.h',
+ ]
+
+@@ -231,9 +248,15 @@ foreach h : check_headers
+ endif
+ endforeach
+
++if cc.has_header('valgrind/memcheck.h', required: get_option('valgrind'))
++ cdata.set('HAVE_VALGRIND_MEMCHECK_H', 1)
++endif
++
+ # FIXME: move this to the above set
+-if cc.has_header('pthread.h')
+- cdata.set('HAVE_PTHREAD', 1)
++if host_machine.system() != 'windows'
++ if cc.has_header('pthread.h')
++ cdata.set('HAVE_PTHREAD', 1)
++ endif
+ endif
+
+ if cc.has_header_symbol('pthread.h', 'PTHREAD_PRIO_INHERIT')
+@@ -293,7 +316,16 @@ check_functions = [
+ foreach f : check_functions
+ if cc.has_function(f)
+ define = 'HAVE_' + f.underscorify().to_upper()
+- cdata.set(define, 1)
++
++ if f == 'posix_memalign' and host_machine.system() == 'windows'
++ message('Win32/mingw32 does not properly define posix_memalign.')
++ elif f == 'fork' and host_machine.system() == 'windows'
++ # __builtin_fork is defined and compiles properly, but calling __builtin_fork() does not.
++ # This causes Meson to think that Windows has a fork() which causes a link error...
++ message('Win32/mingw32 does not properly define fork.')
++ else
++ cdata.set(define, 1)
++ endif
+ endif
+ endforeach
+
+@@ -302,7 +334,11 @@ if cc.has_header_symbol('sys/syscall.h', 'SYS_memfd_create')
+ endif
+
+ if cc.has_function('dgettext')
+- libintl_dep = []
++ if host_machine.system() != 'windows'
++ libintl_dep = []
++ else
++ libintl_dep = cc.find_library('intl')
++ endif
+ else
+ libintl_dep = cc.find_library('intl')
+ endif
+@@ -353,7 +389,12 @@ cdata.set('MESON_BUILD', 1)
+ # On ELF systems we don't want the libraries to be unloaded since we don't clean them up properly,
+ # so we request the nodelete flag to be enabled.
+ # On other systems, we don't really know how to do that, but it's welcome if somebody can tell.
+-nodelete_link_args = ['-Wl,-z,nodelete']
++# Windows doesn't support this flag.
++if host_machine.system() != 'windows'
++ nodelete_link_args = ['-Wl,-z,nodelete']
++else
++ nodelete_link_args = []
++endif
+
+ # Code coverage
+
+@@ -556,7 +597,7 @@ if gio_dep.found()
+ cdata.set('HAVE_GSETTINGS', 1)
+ endif
+
+-glib_dep = dependency('glib-2.0', version : '>= 2.4.0', required: get_option('glib'))
++glib_dep = dependency('glib-2.0', version : '>= 2.28.0', required: get_option('glib'))
+ if glib_dep.found()
+ cdata.set('HAVE_GLIB', 1)
+ endif
+@@ -609,11 +650,13 @@ if x11_dep.found()
+ sm_dep = dependency('sm', required : true)
+ xtst_dep = dependency('xtst', required : true)
+ cdata.set('HAVE_X11', 1)
++ if cc.has_function('XSetIOErrorExitHandler', dependencies: x11_dep)
++ cdata.set('HAVE_XSETIOERROREXITHANDLER', 1)
++ endif
+ endif
+
+ # Module dependencies
+-
+-if cc.has_header('sys/soundcard.h')
++if cc.has_header('sys/soundcard.h', required: get_option('oss-output'))
+ cdata.set('HAVE_OSS_OUTPUT', 1)
+ cdata.set('HAVE_OSS_WRAPPER', 1)
+ cdata.set('PULSEDSP_LOCATION', pulsedsp_location)
+@@ -807,8 +850,9 @@ summary = [
+ '',
+ 'Enable memfd shared memory: @0@'.format(cdata.has('HAVE_MEMFD')),
+ 'Enable X11: @0@'.format(x11_dep.found()),
+-# 'Enable OSS Output: @0@'.format(${ENABLE_OSS_OUTPUT}),
+-# 'Enable OSS Wrapper: @0@'.format(${ENABLE_OSS_WRAPPER}),
++ ' Safe X11 I/O errors: @0@'.format(cdata.has('HAVE_XSETIOERROREXITHANDLER')),
++ 'Enable OSS Output: @0@'.format(cdata.has('HAVE_OSS_OUTPUT')),
++ 'Enable OSS Wrapper: @0@'.format(cdata.has('HAVE_OSS_WRAPPER')),
+ # 'Enable EsounD: @0@'.format(${ENABLE_ESOUND}),
+ 'Enable Alsa: @0@'.format(alsa_dep.found()),
+ # 'Enable CoreAudio: @0@'.format(${ENABLE_COREAUDIO}),
+@@ -841,6 +885,7 @@ summary = [
+ 'Enable SoXR (resampler): @0@'.format(soxr_dep.found()),
+ 'Enable WebRTC echo canceller: @0@'.format(webrtc_dep.found()),
+ 'Enable Gcov coverage: @0@'.format(get_option('gcov')),
++ 'Enable Valgrind: @0@'.format(cdata.has('HAVE_VALGRIND_MEMCHECK_H')),
+ 'Enable man pages: @0@'.format(get_option('man')),
+ 'Enable unit tests: @0@'.format(get_option('tests')),
+ '',
+diff --git a/meson_options.txt b/meson_options.txt
+index 824f24e08..ccfa2f7eb 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -123,6 +123,9 @@ option('openssl',
+ option('orc',
+ type : 'feature', value : 'auto',
+ description : 'Optimized Inner Loop Runtime Compiler')
++option('oss-output',
++ type : 'feature', value : 'auto',
++ description : 'Optional OSS output support')
+ option('samplerate',
+ type : 'feature', value : 'disabled',
+ description : 'Optional libsamplerate support (DEPRECATED)')
+@@ -138,6 +141,9 @@ option('systemd',
+ option('udev',
+ type : 'feature', value : 'auto',
+ description : 'Optional udev support')
++option('valgrind',
++ type : 'feature', value : 'auto',
++ description : 'Optional Valgrind support')
+ option('x11',
+ type : 'feature', value : 'auto',
+ description : 'Optional X11 support')
+diff --git a/po/pulseaudio.pot b/po/pulseaudio.pot
+index b02286acb..7e2f604dd 100644
+--- a/po/pulseaudio.pot
++++ b/po/pulseaudio.pot
+@@ -8,7 +8,7 @@ msgid ""
+ msgstr ""
+ "Project-Id-Version: pulseaudio\n"
+ "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/new\n"
+-"POT-Creation-Date: 2020-11-21 16:47+0300\n"
++"POT-Creation-Date: 2020-12-08 13:51+0200\n"
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+ "Last-Translator: FULL NAME \n"
+ "Language-Team: LANGUAGE \n"
+@@ -337,91 +337,91 @@ msgstr ""
+ msgid "System wide mode unsupported on this platform."
+ msgstr ""
+
+-#: src/daemon/main.c:495
++#: src/daemon/main.c:501
+ msgid "Failed to parse command line."
+ msgstr ""
+
+-#: src/daemon/main.c:534
++#: src/daemon/main.c:540
+ msgid ""
+ "System mode refused for non-root user. Only starting the D-Bus server lookup service."
+ msgstr ""
+
+-#: src/daemon/main.c:633
++#: src/daemon/main.c:639
+ #, c-format
+ msgid "Failed to kill daemon: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:662
++#: src/daemon/main.c:668
+ msgid "This program is not intended to be run as root (unless --system is specified)."
+ msgstr ""
+
+-#: src/daemon/main.c:665
++#: src/daemon/main.c:671
+ msgid "Root privileges required."
+ msgstr ""
+
+-#: src/daemon/main.c:672
++#: src/daemon/main.c:678
+ msgid "--start not supported for system instances."
+ msgstr ""
+
+-#: src/daemon/main.c:712
++#: src/daemon/main.c:718
+ #, c-format
+ msgid "User-configured server at %s, refusing to start/autospawn."
+ msgstr ""
+
+-#: src/daemon/main.c:718
++#: src/daemon/main.c:724
+ #, c-format
+ msgid "User-configured server at %s, which appears to be local. Probing deeper."
+ msgstr ""
+
+-#: src/daemon/main.c:723
++#: src/daemon/main.c:729
+ msgid "Running in system mode, but --disallow-exit not set."
+ msgstr ""
+
+-#: src/daemon/main.c:726
++#: src/daemon/main.c:732
+ msgid "Running in system mode, but --disallow-module-loading not set."
+ msgstr ""
+
+-#: src/daemon/main.c:729
++#: src/daemon/main.c:735
+ msgid "Running in system mode, forcibly disabling SHM mode."
+ msgstr ""
+
+-#: src/daemon/main.c:734
++#: src/daemon/main.c:740
+ msgid "Running in system mode, forcibly disabling exit idle time."
+ msgstr ""
+
+-#: src/daemon/main.c:767
++#: src/daemon/main.c:773
+ msgid "Failed to acquire stdio."
+ msgstr ""
+
+-#: src/daemon/main.c:773 src/daemon/main.c:844
++#: src/daemon/main.c:779 src/daemon/main.c:850
+ #, c-format
+ msgid "pipe() failed: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:778 src/daemon/main.c:849
++#: src/daemon/main.c:784 src/daemon/main.c:855
+ #, c-format
+ msgid "fork() failed: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:793 src/daemon/main.c:864 src/utils/pacat.c:562
++#: src/daemon/main.c:799 src/daemon/main.c:870 src/utils/pacat.c:562
+ #, c-format
+ msgid "read() failed: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:799
++#: src/daemon/main.c:805
+ msgid "Daemon startup failed."
+ msgstr ""
+
+-#: src/daemon/main.c:832
++#: src/daemon/main.c:838
+ #, c-format
+ msgid "setsid() failed: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:965
++#: src/daemon/main.c:971
+ msgid "Failed to get machine ID"
+ msgstr ""
+
+-#: src/daemon/main.c:991
++#: src/daemon/main.c:997
+ msgid ""
+ "OK, so you are running PA in system mode. Please make sure that you actually do want to "
+ "do that.\n"
+@@ -429,26 +429,26 @@ msgid ""
+ "WhatIsWrongWithSystemWide/ for an explanation why system mode is usually a bad idea."
+ msgstr ""
+
+-#: src/daemon/main.c:1007
++#: src/daemon/main.c:1013
+ msgid "pa_pid_file_create() failed."
+ msgstr ""
+
+-#: src/daemon/main.c:1039
++#: src/daemon/main.c:1045
+ msgid "pa_core_new() failed."
+ msgstr ""
+
+-#: src/daemon/main.c:1114
++#: src/daemon/main.c:1120
+ msgid "command line arguments"
+ msgstr ""
+
+-#: src/daemon/main.c:1121
++#: src/daemon/main.c:1127
+ #, c-format
+ msgid ""
+ "Failed to initialize daemon due to errors while executing startup commands. Source of "
+ "commands: %s"
+ msgstr ""
+
+-#: src/daemon/main.c:1126
++#: src/daemon/main.c:1132
+ msgid "Daemon startup without any loaded modules, refusing to work."
+ msgstr ""
+
+@@ -502,12 +502,12 @@ msgid "Internal Microphone"
+ msgstr ""
+
+ #: src/modules/alsa/alsa-mixer.c:2631 src/modules/alsa/alsa-mixer.c:2717
+-#: src/utils/pactl.c:260
++#: src/utils/pactl.c:265
+ msgid "Radio"
+ msgstr ""
+
+ #: src/modules/alsa/alsa-mixer.c:2632 src/modules/alsa/alsa-mixer.c:2718
+-#: src/utils/pactl.c:261
++#: src/utils/pactl.c:266
+ msgid "Video"
+ msgstr ""
+
+@@ -544,12 +544,12 @@ msgid "No Bass Boost"
+ msgstr ""
+
+ #: src/modules/alsa/alsa-mixer.c:2641 src/modules/bluetooth/module-bluez5-device.c:1800
+-#: src/utils/pactl.c:250
++#: src/utils/pactl.c:255
+ msgid "Speaker"
+ msgstr ""
+
+ #: src/modules/alsa/alsa-mixer.c:2642 src/modules/alsa/alsa-mixer.c:2720
+-#: src/utils/pactl.c:251
++#: src/utils/pactl.c:256
+ msgid "Headphones"
+ msgstr ""
+
+@@ -617,136 +617,153 @@ msgstr ""
+ msgid "Analog Mono"
+ msgstr ""
+
++#: src/modules/alsa/alsa-mixer.c:4395
++msgid "Analog Mono (Left)"
++msgstr ""
++
++#: src/modules/alsa/alsa-mixer.c:4396
++msgid "Analog Mono (Right)"
++msgstr ""
++
+ #. Note: Not translated to "Analog Stereo Input", because the source
+ #. * name gets "Input" appended to it automatically, so adding "Input"
+ #. * here would lead to the source name to become "Analog Stereo Input
+ #. * Input". The same logic applies to analog-stereo-output,
+ #. * multichannel-input and multichannel-output.
+-#: src/modules/alsa/alsa-mixer.c:4395 src/modules/alsa/alsa-mixer.c:4403
+-#: src/modules/alsa/alsa-mixer.c:4404
++#: src/modules/alsa/alsa-mixer.c:4397 src/modules/alsa/alsa-mixer.c:4405
++#: src/modules/alsa/alsa-mixer.c:4406
+ msgid "Analog Stereo"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4396 src/pulse/channelmap.c:103
++#: src/modules/alsa/alsa-mixer.c:4398 src/pulse/channelmap.c:103
+ #: src/pulse/channelmap.c:771
+ msgid "Mono"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4397 src/pulse/channelmap.c:775
++#: src/modules/alsa/alsa-mixer.c:4399 src/pulse/channelmap.c:775
+ msgid "Stereo"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4405 src/modules/alsa/alsa-mixer.c:4406
++#: src/modules/alsa/alsa-mixer.c:4407 src/modules/alsa/alsa-mixer.c:4565
++#: src/modules/bluetooth/module-bluez5-device.c:1780 src/utils/pactl.c:259
++msgid "Headset"
++msgstr ""
++
++#: src/modules/alsa/alsa-mixer.c:4408 src/modules/alsa/alsa-mixer.c:4566
++msgid "Speakerphone"
++msgstr ""
++
++#: src/modules/alsa/alsa-mixer.c:4409 src/modules/alsa/alsa-mixer.c:4410
+ msgid "Multichannel"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4407
++#: src/modules/alsa/alsa-mixer.c:4411
+ msgid "Analog Surround 2.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4408
++#: src/modules/alsa/alsa-mixer.c:4412
+ msgid "Analog Surround 3.0"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4409
++#: src/modules/alsa/alsa-mixer.c:4413
+ msgid "Analog Surround 3.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4410
++#: src/modules/alsa/alsa-mixer.c:4414
+ msgid "Analog Surround 4.0"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4411
++#: src/modules/alsa/alsa-mixer.c:4415
+ msgid "Analog Surround 4.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4412
++#: src/modules/alsa/alsa-mixer.c:4416
+ msgid "Analog Surround 5.0"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4413
++#: src/modules/alsa/alsa-mixer.c:4417
+ msgid "Analog Surround 5.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4414
++#: src/modules/alsa/alsa-mixer.c:4418
+ msgid "Analog Surround 6.0"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4415
++#: src/modules/alsa/alsa-mixer.c:4419
+ msgid "Analog Surround 6.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4416
++#: src/modules/alsa/alsa-mixer.c:4420
+ msgid "Analog Surround 7.0"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4417
++#: src/modules/alsa/alsa-mixer.c:4421
+ msgid "Analog Surround 7.1"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4418
++#: src/modules/alsa/alsa-mixer.c:4422
+ msgid "Digital Stereo (IEC958)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4419
++#: src/modules/alsa/alsa-mixer.c:4423
+ msgid "Digital Surround 4.0 (IEC958/AC3)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4420
++#: src/modules/alsa/alsa-mixer.c:4424
+ msgid "Digital Surround 5.1 (IEC958/AC3)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4421
++#: src/modules/alsa/alsa-mixer.c:4425
+ msgid "Digital Surround 5.1 (IEC958/DTS)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4422
++#: src/modules/alsa/alsa-mixer.c:4426
+ msgid "Digital Stereo (HDMI)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4423
++#: src/modules/alsa/alsa-mixer.c:4427
+ msgid "Digital Surround 5.1 (HDMI)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4424
++#: src/modules/alsa/alsa-mixer.c:4428
+ msgid "Chat"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4425
++#: src/modules/alsa/alsa-mixer.c:4429
+ msgid "Game"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4559
++#: src/modules/alsa/alsa-mixer.c:4563
+ msgid "Analog Mono Duplex"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4560
++#: src/modules/alsa/alsa-mixer.c:4564
+ msgid "Analog Stereo Duplex"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4561
++#: src/modules/alsa/alsa-mixer.c:4567
+ msgid "Digital Stereo Duplex (IEC958)"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4562
++#: src/modules/alsa/alsa-mixer.c:4568
+ msgid "Multichannel Duplex"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4563
++#: src/modules/alsa/alsa-mixer.c:4569
+ msgid "Stereo Duplex"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4564 src/modules/alsa/module-alsa-card.c:188
++#: src/modules/alsa/alsa-mixer.c:4570 src/modules/alsa/module-alsa-card.c:188
+ #: src/modules/bluetooth/module-bluez5-device.c:2053
+ msgid "Off"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4664
++#: src/modules/alsa/alsa-mixer.c:4670
+ #, c-format
+ msgid "%s Output"
+ msgstr ""
+
+-#: src/modules/alsa/alsa-mixer.c:4672
++#: src/modules/alsa/alsa-mixer.c:4678
+ #, c-format
+ msgid "%s Input"
+ msgstr ""
+@@ -831,11 +848,7 @@ msgstr ""
+ msgid "Bluetooth Output"
+ msgstr ""
+
+-#: src/modules/bluetooth/module-bluez5-device.c:1780 src/utils/pactl.c:254
+-msgid "Headset"
+-msgstr ""
+-
+-#: src/modules/bluetooth/module-bluez5-device.c:1786 src/utils/pactl.c:265
++#: src/modules/bluetooth/module-bluez5-device.c:1786 src/utils/pactl.c:270
+ msgid "Handsfree"
+ msgstr ""
+
+@@ -843,19 +856,19 @@ msgstr ""
+ msgid "Headphone"
+ msgstr ""
+
+-#: src/modules/bluetooth/module-bluez5-device.c:1813 src/utils/pactl.c:264
++#: src/modules/bluetooth/module-bluez5-device.c:1813 src/utils/pactl.c:269
+ msgid "Portable"
+ msgstr ""
+
+-#: src/modules/bluetooth/module-bluez5-device.c:1819 src/utils/pactl.c:266
++#: src/modules/bluetooth/module-bluez5-device.c:1819 src/utils/pactl.c:271
+ msgid "Car"
+ msgstr ""
+
+-#: src/modules/bluetooth/module-bluez5-device.c:1825 src/utils/pactl.c:267
++#: src/modules/bluetooth/module-bluez5-device.c:1825 src/utils/pactl.c:272
+ msgid "HiFi"
+ msgstr ""
+
+-#: src/modules/bluetooth/module-bluez5-device.c:1831 src/utils/pactl.c:268
++#: src/modules/bluetooth/module-bluez5-device.c:1831 src/utils/pactl.c:273
+ msgid "Phone"
+ msgstr ""
+
+@@ -946,11 +959,11 @@ msgstr ""
+ msgid "Clocked NULL sink"
+ msgstr ""
+
+-#: src/modules/module-null-sink.c:333
++#: src/modules/module-null-sink.c:334
+ msgid "Null Output"
+ msgstr ""
+
+-#: src/modules/module-null-sink.c:345 src/utils/pactl.c:1096
++#: src/modules/module-null-sink.c:346 src/utils/pactl.c:1170
+ #, c-format
+ msgid "Failed to set format: invalid format string %s"
+ msgstr ""
+@@ -981,17 +994,18 @@ msgstr ""
+ msgid "Tunnel to %s/%s"
+ msgstr ""
+
+-#: src/modules/module-virtual-surround-sink.c:45
++#: src/modules/module-virtual-surround-sink.c:50
+ msgid "Virtual surround sink"
+ msgstr ""
+
+-#: src/modules/module-virtual-surround-sink.c:49
++#: src/modules/module-virtual-surround-sink.c:54
+ msgid ""
+ "sink_name= sink_properties= master= sink_master= format= "
+ "rate= channels= channel_map= "
+ "use_volume_sharing= force_flat_volume= hrir=/path/to/left_hrir."
+-"wav autoloaded= "
++"wav hrir_left=/path/to/left_hrir.wav hrir_right=/path/to/optional/right_hrir.wav "
++"autoloaded= "
+ msgstr ""
+
+ #: src/modules/raop/module-raop-discover.c:295
+@@ -1588,7 +1602,7 @@ msgstr ""
+ msgid "pa_stream_connect_record() failed: %s"
+ msgstr ""
+
+-#: src/utils/pacat.c:514 src/utils/pactl.c:1492
++#: src/utils/pacat.c:514 src/utils/pactl.c:1572
+ #, c-format
+ msgid "Connection failure: %s"
+ msgstr ""
+@@ -1709,7 +1723,7 @@ msgid ""
+ "Linked with libpulse %s\n"
+ msgstr ""
+
+-#: src/utils/pacat.c:852 src/utils/pactl.c:1694
++#: src/utils/pacat.c:852 src/utils/pactl.c:1775
+ #, c-format
+ msgid "Invalid client name '%s'"
+ msgstr ""
+@@ -1780,7 +1794,7 @@ msgid ""
+ "file."
+ msgstr ""
+
+-#: src/utils/pacat.c:1079 src/utils/pactl.c:1758
++#: src/utils/pacat.c:1079 src/utils/pactl.c:1840
+ msgid "Failed to determine sample specification from file."
+ msgstr ""
+
+@@ -1813,7 +1827,7 @@ msgstr ""
+ msgid "Failed to set media name."
+ msgstr ""
+
+-#: src/utils/pacat.c:1160 src/utils/pactl.c:2108
++#: src/utils/pacat.c:1160 src/utils/pactl.c:2206
+ msgid "pa_mainloop_new() failed."
+ msgstr ""
+
+@@ -1821,11 +1835,11 @@ msgstr ""
+ msgid "io_new() failed."
+ msgstr ""
+
+-#: src/utils/pacat.c:1190 src/utils/pactl.c:2120
++#: src/utils/pacat.c:1190 src/utils/pactl.c:2218
+ msgid "pa_context_new() failed."
+ msgstr ""
+
+-#: src/utils/pacat.c:1198 src/utils/pactl.c:2126
++#: src/utils/pacat.c:1198 src/utils/pactl.c:2224
+ #, c-format
+ msgid "pa_context_connect() failed: %s"
+ msgstr ""
+@@ -1834,19 +1848,19 @@ msgstr ""
+ msgid "pa_context_rttime_new() failed."
+ msgstr ""
+
+-#: src/utils/pacat.c:1211 src/utils/pactl.c:2131
++#: src/utils/pacat.c:1211 src/utils/pactl.c:2229
+ msgid "pa_mainloop_run() failed."
+ msgstr ""
+
+-#: src/utils/pacmd.c:51 src/utils/pactl.c:1616
++#: src/utils/pacmd.c:51 src/utils/pactl.c:1696
+ msgid "NAME [ARGS ...]"
+ msgstr ""
+
+-#: src/utils/pacmd.c:52 src/utils/pacmd.c:60 src/utils/pactl.c:1617
++#: src/utils/pacmd.c:52 src/utils/pacmd.c:60 src/utils/pactl.c:1697
+ msgid "NAME|#N"
+ msgstr ""
+
+-#: src/utils/pacmd.c:53 src/utils/pacmd.c:63 src/utils/pactl.c:1615 src/utils/pactl.c:1621
++#: src/utils/pacmd.c:53 src/utils/pacmd.c:63 src/utils/pactl.c:1695 src/utils/pactl.c:1701
+ msgid "NAME"
+ msgstr ""
+
+@@ -1858,7 +1872,7 @@ msgstr ""
+ msgid "#N VOLUME"
+ msgstr ""
+
+-#: src/utils/pacmd.c:56 src/utils/pacmd.c:70 src/utils/pactl.c:1619
++#: src/utils/pacmd.c:56 src/utils/pacmd.c:70 src/utils/pactl.c:1699
+ msgid "NAME|#N 1|0"
+ msgstr ""
+
+@@ -1894,7 +1908,7 @@ msgstr ""
+ msgid "FILENAME SINK|#N"
+ msgstr ""
+
+-#: src/utils/pacmd.c:69 src/utils/pactl.c:1618
++#: src/utils/pacmd.c:69 src/utils/pactl.c:1698
+ msgid "#N SINK|SOURCE"
+ msgstr ""
+
+@@ -1902,15 +1916,15 @@ msgstr ""
+ msgid "1|0"
+ msgstr ""
+
+-#: src/utils/pacmd.c:72 src/utils/pactl.c:1620
++#: src/utils/pacmd.c:72 src/utils/pactl.c:1700
+ msgid "CARD PROFILE"
+ msgstr ""
+
+-#: src/utils/pacmd.c:73 src/utils/pactl.c:1622
++#: src/utils/pacmd.c:73 src/utils/pactl.c:1702
+ msgid "NAME|#N PORT"
+ msgstr ""
+
+-#: src/utils/pacmd.c:74 src/utils/pactl.c:1628
++#: src/utils/pacmd.c:74 src/utils/pactl.c:1708
+ msgid "CARD-NAME|CARD-#N PORT OFFSET"
+ msgstr ""
+
+@@ -1926,7 +1940,11 @@ msgstr ""
+ msgid "FRAMES"
+ msgstr ""
+
+-#: src/utils/pacmd.c:81
++#: src/utils/pacmd.c:80 src/utils/pactl.c:1709
++msgid "RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"
++msgstr ""
++
++#: src/utils/pacmd.c:82
+ #, c-format
+ msgid ""
+ "\n"
+@@ -1935,7 +1953,7 @@ msgid ""
+ "When no command is given pacmd starts in the interactive mode.\n"
+ msgstr ""
+
+-#: src/utils/pacmd.c:128
++#: src/utils/pacmd.c:129
+ #, c-format
+ msgid ""
+ "pacmd %s\n"
+@@ -1943,73 +1961,73 @@ msgid ""
+ "Linked with libpulse %s\n"
+ msgstr ""
+
+-#: src/utils/pacmd.c:142
++#: src/utils/pacmd.c:143
+ msgid "No PulseAudio daemon running, or not running as session daemon."
+ msgstr ""
+
+-#: src/utils/pacmd.c:147
++#: src/utils/pacmd.c:148
+ #, c-format
+ msgid "socket(PF_UNIX, SOCK_STREAM, 0): %s"
+ msgstr ""
+
+-#: src/utils/pacmd.c:164
++#: src/utils/pacmd.c:165
+ #, c-format
+ msgid "connect(): %s"
+ msgstr ""
+
+-#: src/utils/pacmd.c:172
++#: src/utils/pacmd.c:173
+ msgid "Failed to kill PulseAudio daemon."
+ msgstr ""
+
+-#: src/utils/pacmd.c:180
++#: src/utils/pacmd.c:181
+ msgid "Daemon not responding."
+ msgstr ""
+
+-#: src/utils/pacmd.c:212 src/utils/pacmd.c:321 src/utils/pacmd.c:339
++#: src/utils/pacmd.c:213 src/utils/pacmd.c:322 src/utils/pacmd.c:340
+ #, c-format
+ msgid "write(): %s"
+ msgstr ""
+
+-#: src/utils/pacmd.c:268
++#: src/utils/pacmd.c:269
+ #, c-format
+ msgid "poll(): %s"
+ msgstr ""
+
+-#: src/utils/pacmd.c:279 src/utils/pacmd.c:299
++#: src/utils/pacmd.c:280 src/utils/pacmd.c:300
+ #, c-format
+ msgid "read(): %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:164
++#: src/utils/pactl.c:169
+ #, c-format
+ msgid "Failed to get statistics: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:170
++#: src/utils/pactl.c:175
+ #, c-format
+ msgid "Currently in use: %u block containing %s bytes total.\n"
+ msgid_plural "Currently in use: %u blocks containing %s bytes total.\n"
+ msgstr[0] ""
+ msgstr[1] ""
+
+-#: src/utils/pactl.c:176
++#: src/utils/pactl.c:181
+ #, c-format
+ msgid "Allocated during whole lifetime: %u block containing %s bytes total.\n"
+ msgid_plural "Allocated during whole lifetime: %u blocks containing %s bytes total.\n"
+ msgstr[0] ""
+ msgstr[1] ""
+
+-#: src/utils/pactl.c:182
++#: src/utils/pactl.c:187
+ #, c-format
+ msgid "Sample cache size: %s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:191
++#: src/utils/pactl.c:196
+ #, c-format
+ msgid "Failed to get server information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:196
++#: src/utils/pactl.c:201
+ #, c-format
+ msgid ""
+ "Server String: %s\n"
+@@ -2020,7 +2038,7 @@ msgid ""
+ "Tile Size: %zu\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:212
++#: src/utils/pactl.c:217
+ #, c-format
+ msgid ""
+ "User Name: %s\n"
+@@ -2034,76 +2052,76 @@ msgid ""
+ "Cookie: %04x:%04x\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:237
++#: src/utils/pactl.c:242
+ msgid "availability unknown"
+ msgstr ""
+
+-#: src/utils/pactl.c:238
++#: src/utils/pactl.c:243
+ msgid "available"
+ msgstr ""
+
+-#: src/utils/pactl.c:239
++#: src/utils/pactl.c:244
+ msgid "not available"
+ msgstr ""
+
+-#: src/utils/pactl.c:248 src/utils/pactl.c:272
++#: src/utils/pactl.c:253 src/utils/pactl.c:277
+ msgid "Unknown"
+ msgstr ""
+
+-#: src/utils/pactl.c:249
++#: src/utils/pactl.c:254
+ msgid "Aux"
+ msgstr ""
+
+-#: src/utils/pactl.c:252
++#: src/utils/pactl.c:257
+ msgid "Line"
+ msgstr ""
+
+-#: src/utils/pactl.c:253
++#: src/utils/pactl.c:258
+ msgid "Mic"
+ msgstr ""
+
+-#: src/utils/pactl.c:255
++#: src/utils/pactl.c:260
+ msgid "Handset"
+ msgstr ""
+
+-#: src/utils/pactl.c:256
++#: src/utils/pactl.c:261
+ msgid "Earpiece"
+ msgstr ""
+
+-#: src/utils/pactl.c:257
++#: src/utils/pactl.c:262
+ msgid "SPDIF"
+ msgstr ""
+
+-#: src/utils/pactl.c:258
++#: src/utils/pactl.c:263
+ msgid "HDMI"
+ msgstr ""
+
+-#: src/utils/pactl.c:259
++#: src/utils/pactl.c:264
+ msgid "TV"
+ msgstr ""
+
+-#: src/utils/pactl.c:262
++#: src/utils/pactl.c:267
+ msgid "USB"
+ msgstr ""
+
+-#: src/utils/pactl.c:263
++#: src/utils/pactl.c:268
+ msgid "Bluetooth"
+ msgstr ""
+
+-#: src/utils/pactl.c:269
++#: src/utils/pactl.c:274
+ msgid "Network"
+ msgstr ""
+
+-#: src/utils/pactl.c:270
++#: src/utils/pactl.c:275
+ msgid "Analog"
+ msgstr ""
+
+-#: src/utils/pactl.c:294 src/utils/pactl.c:946 src/utils/pactl.c:1024
++#: src/utils/pactl.c:299 src/utils/pactl.c:1020 src/utils/pactl.c:1098
+ #, c-format
+ msgid "Failed to get sink information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:320
++#: src/utils/pactl.c:325
+ #, c-format
+ msgid ""
+ "Sink #%u\n"
+@@ -2125,36 +2143,36 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:364 src/utils/pactl.c:472 src/utils/pactl.c:635
++#: src/utils/pactl.c:369 src/utils/pactl.c:477 src/utils/pactl.c:640
+ #, c-format
+ msgid "\tPorts:\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:366 src/utils/pactl.c:474
++#: src/utils/pactl.c:371 src/utils/pactl.c:479
+ #, c-format
+ msgid "\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:368 src/utils/pactl.c:476 src/utils/pactl.c:640
++#: src/utils/pactl.c:373 src/utils/pactl.c:481 src/utils/pactl.c:645
+ msgid ", availability group: "
+ msgstr ""
+
+-#: src/utils/pactl.c:373 src/utils/pactl.c:481
++#: src/utils/pactl.c:378 src/utils/pactl.c:486
+ #, c-format
+ msgid "\tActive Port: %s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:379 src/utils/pactl.c:487
++#: src/utils/pactl.c:384 src/utils/pactl.c:492
+ #, c-format
+ msgid "\tFormats:\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:403 src/utils/pactl.c:966 src/utils/pactl.c:1039
++#: src/utils/pactl.c:408 src/utils/pactl.c:1040 src/utils/pactl.c:1113
+ #, c-format
+ msgid "Failed to get source information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:429
++#: src/utils/pactl.c:434
+ #, c-format
+ msgid ""
+ "Source #%u\n"
+@@ -2176,19 +2194,19 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:457 src/utils/pactl.c:529 src/utils/pactl.c:572 src/utils/pactl.c:614
+-#: src/utils/pactl.c:713 src/utils/pactl.c:714 src/utils/pactl.c:725 src/utils/pactl.c:783
+-#: src/utils/pactl.c:784 src/utils/pactl.c:795 src/utils/pactl.c:846 src/utils/pactl.c:847
+-#: src/utils/pactl.c:853
++#: src/utils/pactl.c:462 src/utils/pactl.c:534 src/utils/pactl.c:577 src/utils/pactl.c:619
++#: src/utils/pactl.c:718 src/utils/pactl.c:719 src/utils/pactl.c:730 src/utils/pactl.c:788
++#: src/utils/pactl.c:789 src/utils/pactl.c:800 src/utils/pactl.c:851 src/utils/pactl.c:852
++#: src/utils/pactl.c:858
+ msgid "n/a"
+ msgstr ""
+
+-#: src/utils/pactl.c:498 src/utils/pactl.c:903
++#: src/utils/pactl.c:503 src/utils/pactl.c:977
+ #, c-format
+ msgid "Failed to get module information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:521
++#: src/utils/pactl.c:526
+ #, c-format
+ msgid ""
+ "Module #%u\n"
+@@ -2199,12 +2217,12 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:540
++#: src/utils/pactl.c:545
+ #, c-format
+ msgid "Failed to get client information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:566
++#: src/utils/pactl.c:571
+ #, c-format
+ msgid ""
+ "Client #%u\n"
+@@ -2214,12 +2232,12 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:583
++#: src/utils/pactl.c:588
+ #, c-format
+ msgid "Failed to get card information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:606
++#: src/utils/pactl.c:611
+ #, c-format
+ msgid ""
+ "Card #%u\n"
+@@ -2230,44 +2248,44 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:622
++#: src/utils/pactl.c:627
+ #, c-format
+ msgid "\tProfiles:\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:624
++#: src/utils/pactl.c:629
+ #, c-format
+ msgid "\t\t%s: %s (sinks: %u, sources: %u, priority: %u, available: %s)\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:629
++#: src/utils/pactl.c:634
+ #, c-format
+ msgid "\tActive Profile: %s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:638
++#: src/utils/pactl.c:643
+ #, c-format
+ msgid "\t\t%s: %s (type: %s, priority: %u, latency offset: % usec%s%s, %s)\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:644
++#: src/utils/pactl.c:649
+ #, c-format
+ msgid ""
+ "\t\t\tProperties:\n"
+ "\t\t\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:649
++#: src/utils/pactl.c:654
+ #, c-format
+ msgid "\t\t\tPart of profile(s): %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:666 src/utils/pactl.c:986 src/utils/pactl.c:1054
++#: src/utils/pactl.c:671 src/utils/pactl.c:1060 src/utils/pactl.c:1128
+ #, c-format
+ msgid "Failed to get sink input information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:695
++#: src/utils/pactl.c:700
+ #, c-format
+ msgid ""
+ "Sink Input #%u\n"
+@@ -2289,12 +2307,12 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:736 src/utils/pactl.c:1006 src/utils/pactl.c:1069
++#: src/utils/pactl.c:741 src/utils/pactl.c:1080 src/utils/pactl.c:1143
+ #, c-format
+ msgid "Failed to get source output information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:765
++#: src/utils/pactl.c:770
+ #, c-format
+ msgid ""
+ "Source Output #%u\n"
+@@ -2316,12 +2334,12 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:806
++#: src/utils/pactl.c:811
+ #, c-format
+ msgid "Failed to get sample information: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:833
++#: src/utils/pactl.c:838
+ #, c-format
+ msgid ""
+ "Sample #%u\n"
+@@ -2338,17 +2356,31 @@ msgid ""
+ "\t\t%s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:861 src/utils/pactl.c:871
++#: src/utils/pactl.c:866 src/utils/pactl.c:876
+ #, c-format
+ msgid "Failure: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:910
++#: src/utils/pactl.c:889
++#, c-format
++msgid "Send message failed: %s"
++msgstr ""
++
++#: src/utils/pactl.c:906
++#, c-format
++msgid "list-handlers message failed: %s"
++msgstr ""
++
++#: src/utils/pactl.c:912 src/utils/pactl.c:947
++msgid "list-handlers message response could not be parsed correctly"
++msgstr ""
++
++#: src/utils/pactl.c:984
+ #, c-format
+ msgid "Failed to unload module: Module %s not loaded"
+ msgstr ""
+
+-#: src/utils/pactl.c:928
++#: src/utils/pactl.c:1002
+ #, c-format
+ msgid ""
+ "Failed to set volume: You tried to set volumes for %d channel, whereas channel(s) "
+@@ -2359,135 +2391,136 @@ msgid_plural ""
+ msgstr[0] ""
+ msgstr[1] ""
+
+-#: src/utils/pactl.c:1139
++#: src/utils/pactl.c:1213
+ #, c-format
+ msgid "Failed to upload sample: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:1156
++#: src/utils/pactl.c:1230
+ msgid "Premature end of file"
+ msgstr ""
+
+-#: src/utils/pactl.c:1176
++#: src/utils/pactl.c:1250
+ msgid "new"
+ msgstr ""
+
+-#: src/utils/pactl.c:1179
++#: src/utils/pactl.c:1253
+ msgid "change"
+ msgstr ""
+
+-#: src/utils/pactl.c:1182
++#: src/utils/pactl.c:1256
+ msgid "remove"
+ msgstr ""
+
+-#: src/utils/pactl.c:1185 src/utils/pactl.c:1220
++#: src/utils/pactl.c:1259 src/utils/pactl.c:1294
+ msgid "unknown"
+ msgstr ""
+
+-#: src/utils/pactl.c:1193
++#: src/utils/pactl.c:1267
+ msgid "sink"
+ msgstr ""
+
+-#: src/utils/pactl.c:1196
++#: src/utils/pactl.c:1270
+ msgid "source"
+ msgstr ""
+
+-#: src/utils/pactl.c:1199
++#: src/utils/pactl.c:1273
+ msgid "sink-input"
+ msgstr ""
+
+-#: src/utils/pactl.c:1202
++#: src/utils/pactl.c:1276
+ msgid "source-output"
+ msgstr ""
+
+-#: src/utils/pactl.c:1205
++#: src/utils/pactl.c:1279
+ msgid "module"
+ msgstr ""
+
+-#: src/utils/pactl.c:1208
++#: src/utils/pactl.c:1282
+ msgid "client"
+ msgstr ""
+
+-#: src/utils/pactl.c:1211
++#: src/utils/pactl.c:1285
+ msgid "sample-cache"
+ msgstr ""
+
+-#: src/utils/pactl.c:1214
++#: src/utils/pactl.c:1288
+ msgid "server"
+ msgstr ""
+
+-#: src/utils/pactl.c:1217
++#: src/utils/pactl.c:1291
+ msgid "card"
+ msgstr ""
+
+-#: src/utils/pactl.c:1226
++#: src/utils/pactl.c:1300
+ #, c-format
+ msgid "Event '%s' on %s #%u\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1498
++#: src/utils/pactl.c:1578
+ msgid "Got SIGINT, exiting."
+ msgstr ""
+
+-#: src/utils/pactl.c:1531
++#: src/utils/pactl.c:1611
+ msgid "Invalid volume specification"
+ msgstr ""
+
+-#: src/utils/pactl.c:1554
++#: src/utils/pactl.c:1634
+ msgid "Volume outside permissible range.\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1567
++#: src/utils/pactl.c:1647
+ msgid "Invalid number of volume specifications.\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1579
++#: src/utils/pactl.c:1659
+ msgid "Inconsistent volume specification.\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1609 src/utils/pactl.c:1610 src/utils/pactl.c:1611
+-#: src/utils/pactl.c:1612 src/utils/pactl.c:1613 src/utils/pactl.c:1614
+-#: src/utils/pactl.c:1615 src/utils/pactl.c:1616 src/utils/pactl.c:1617
+-#: src/utils/pactl.c:1618 src/utils/pactl.c:1619 src/utils/pactl.c:1620
+-#: src/utils/pactl.c:1621 src/utils/pactl.c:1622 src/utils/pactl.c:1623
+-#: src/utils/pactl.c:1624 src/utils/pactl.c:1625 src/utils/pactl.c:1626
+-#: src/utils/pactl.c:1627 src/utils/pactl.c:1628 src/utils/pactl.c:1629
++#: src/utils/pactl.c:1689 src/utils/pactl.c:1690 src/utils/pactl.c:1691
++#: src/utils/pactl.c:1692 src/utils/pactl.c:1693 src/utils/pactl.c:1694
++#: src/utils/pactl.c:1695 src/utils/pactl.c:1696 src/utils/pactl.c:1697
++#: src/utils/pactl.c:1698 src/utils/pactl.c:1699 src/utils/pactl.c:1700
++#: src/utils/pactl.c:1701 src/utils/pactl.c:1702 src/utils/pactl.c:1703
++#: src/utils/pactl.c:1704 src/utils/pactl.c:1705 src/utils/pactl.c:1706
++#: src/utils/pactl.c:1707 src/utils/pactl.c:1708 src/utils/pactl.c:1709
++#: src/utils/pactl.c:1710
+ msgid "[options]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1611
++#: src/utils/pactl.c:1691
+ msgid "[TYPE]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1613
++#: src/utils/pactl.c:1693
+ msgid "FILENAME [NAME]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1614
++#: src/utils/pactl.c:1694
+ msgid "NAME [SINK]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1623
++#: src/utils/pactl.c:1703
+ msgid "NAME|#N VOLUME [VOLUME ...]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1624
++#: src/utils/pactl.c:1704
+ msgid "#N VOLUME [VOLUME ...]"
+ msgstr ""
+
+-#: src/utils/pactl.c:1625
++#: src/utils/pactl.c:1705
+ msgid "NAME|#N 1|0|toggle"
+ msgstr ""
+
+-#: src/utils/pactl.c:1626
++#: src/utils/pactl.c:1706
+ msgid "#N 1|0|toggle"
+ msgstr ""
+
+-#: src/utils/pactl.c:1627
++#: src/utils/pactl.c:1707
+ msgid "#N FORMATS"
+ msgstr ""
+
+-#: src/utils/pactl.c:1630
++#: src/utils/pactl.c:1711
+ #, c-format
+ msgid ""
+ "\n"
+@@ -2495,7 +2528,7 @@ msgid ""
+ "can be used to specify the default sink, source and monitor.\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1633
++#: src/utils/pactl.c:1714
+ #, c-format
+ msgid ""
+ "\n"
+@@ -2506,7 +2539,7 @@ msgid ""
+ " -n, --client-name=NAME How to call this client on the server\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1674
++#: src/utils/pactl.c:1755
+ #, c-format
+ msgid ""
+ "pactl %s\n"
+@@ -2514,146 +2547,156 @@ msgid ""
+ "Linked with libpulse %s\n"
+ msgstr ""
+
+-#: src/utils/pactl.c:1730
++#: src/utils/pactl.c:1812
+ #, c-format
+ msgid "Specify nothing, or one of: %s"
+ msgstr ""
+
+-#: src/utils/pactl.c:1740
++#: src/utils/pactl.c:1822
+ msgid "Please specify a sample file to load"
+ msgstr ""
+
+-#: src/utils/pactl.c:1753
++#: src/utils/pactl.c:1835
+ msgid "Failed to open sound file."
+ msgstr ""
+
+-#: src/utils/pactl.c:1765
++#: src/utils/pactl.c:1847
+ msgid "Warning: Failed to determine sample specification from file."
+ msgstr ""
+
+-#: src/utils/pactl.c:1775
++#: src/utils/pactl.c:1857
+ msgid "You have to specify a sample name to play"
+ msgstr ""
+
+-#: src/utils/pactl.c:1787
++#: src/utils/pactl.c:1869
+ msgid "You have to specify a sample name to remove"
+ msgstr ""
+
+-#: src/utils/pactl.c:1796
++#: src/utils/pactl.c:1878
+ msgid "You have to specify a sink input index and a sink"
+ msgstr ""
+
+-#: src/utils/pactl.c:1806
++#: src/utils/pactl.c:1888
+ msgid "You have to specify a source output index and a source"
+ msgstr ""
+
+-#: src/utils/pactl.c:1821
++#: src/utils/pactl.c:1903
+ msgid "You have to specify a module name and arguments."
+ msgstr ""
+
+-#: src/utils/pactl.c:1841
++#: src/utils/pactl.c:1923
+ msgid "You have to specify a module index or name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1854
++#: src/utils/pactl.c:1936
+ msgid "You may not specify more than one sink. You have to specify a boolean value."
+ msgstr ""
+
+-#: src/utils/pactl.c:1859 src/utils/pactl.c:1879
++#: src/utils/pactl.c:1941 src/utils/pactl.c:1961
+ msgid "Invalid suspend specification."
+ msgstr ""
+
+-#: src/utils/pactl.c:1874
++#: src/utils/pactl.c:1956
+ msgid "You may not specify more than one source. You have to specify a boolean value."
+ msgstr ""
+
+-#: src/utils/pactl.c:1891
++#: src/utils/pactl.c:1973
+ msgid "You have to specify a card name/index and a profile name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1902
++#: src/utils/pactl.c:1984
+ msgid "You have to specify a sink name/index and a port name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1913
++#: src/utils/pactl.c:1995
+ msgid "You have to specify a sink name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1923
++#: src/utils/pactl.c:2005
+ msgid "You have to specify a source name/index and a port name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1934
++#: src/utils/pactl.c:2016
+ msgid "You have to specify a source name"
+ msgstr ""
+
+-#: src/utils/pactl.c:1944
++#: src/utils/pactl.c:2026
+ msgid "You have to specify a sink name/index and a volume"
+ msgstr ""
+
+-#: src/utils/pactl.c:1957
++#: src/utils/pactl.c:2039
+ msgid "You have to specify a source name/index and a volume"
+ msgstr ""
+
+-#: src/utils/pactl.c:1970
++#: src/utils/pactl.c:2052
+ msgid "You have to specify a sink input index and a volume"
+ msgstr ""
+
+-#: src/utils/pactl.c:1975
++#: src/utils/pactl.c:2057
+ msgid "Invalid sink input index"
+ msgstr ""
+
+-#: src/utils/pactl.c:1986
++#: src/utils/pactl.c:2068
+ msgid "You have to specify a source output index and a volume"
+ msgstr ""
+
+-#: src/utils/pactl.c:1991
++#: src/utils/pactl.c:2073
+ msgid "Invalid source output index"
+ msgstr ""
+
+-#: src/utils/pactl.c:2002
++#: src/utils/pactl.c:2084
+ msgid "You have to specify a sink name/index and a mute action (0, 1, or 'toggle')"
+ msgstr ""
+
+-#: src/utils/pactl.c:2007 src/utils/pactl.c:2022 src/utils/pactl.c:2042
+-#: src/utils/pactl.c:2060
++#: src/utils/pactl.c:2089 src/utils/pactl.c:2104 src/utils/pactl.c:2124
++#: src/utils/pactl.c:2142
+ msgid "Invalid mute specification"
+ msgstr ""
+
+-#: src/utils/pactl.c:2017
++#: src/utils/pactl.c:2099
+ msgid "You have to specify a source name/index and a mute action (0, 1, or 'toggle')"
+ msgstr ""
+
+-#: src/utils/pactl.c:2032
++#: src/utils/pactl.c:2114
+ msgid "You have to specify a sink input index and a mute action (0, 1, or 'toggle')"
+ msgstr ""
+
+-#: src/utils/pactl.c:2037
++#: src/utils/pactl.c:2119
+ msgid "Invalid sink input index specification"
+ msgstr ""
+
+-#: src/utils/pactl.c:2050
++#: src/utils/pactl.c:2132
+ msgid "You have to specify a source output index and a mute action (0, 1, or 'toggle')"
+ msgstr ""
+
+-#: src/utils/pactl.c:2055
++#: src/utils/pactl.c:2137
+ msgid "Invalid source output index specification"
+ msgstr ""
+
+-#: src/utils/pactl.c:2072
++#: src/utils/pactl.c:2150
++msgid "You have to specify at least an object path and a message name"
++msgstr ""
++
++#: src/utils/pactl.c:2160
++msgid ""
++"Excess arguments given, they will be ignored. Note that all message parameters must be "
++"given as a single string."
++msgstr ""
++
++#: src/utils/pactl.c:2170
+ msgid ""
+ "You have to specify a sink index and a semicolon-separated list of supported formats"
+ msgstr ""
+
+-#: src/utils/pactl.c:2084
++#: src/utils/pactl.c:2182
+ msgid "You have to specify a card name/index, a port name and a latency offset"
+ msgstr ""
+
+-#: src/utils/pactl.c:2091
++#: src/utils/pactl.c:2189
+ msgid "Could not parse latency offset"
+ msgstr ""
+
+-#: src/utils/pactl.c:2103
++#: src/utils/pactl.c:2201
+ msgid "No valid command specified."
+ msgstr ""
+
+diff --git a/po/sk.po b/po/sk.po
+index 99f898351..a7b096d91 100644
+--- a/po/sk.po
++++ b/po/sk.po
+@@ -9,7 +9,7 @@ msgstr ""
+ "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
+ "issues/new\n"
+ "POT-Creation-Date: 2016-06-22 13:54+0000\n"
+-"PO-Revision-Date: 2020-10-07 02:48+0000\n"
++"PO-Revision-Date: 2020-11-25 08:35+0000\n"
+ "Last-Translator: Dusan Kazik \n"
+ "Language-Team: Slovak \n"
+@@ -18,7 +18,7 @@ msgstr ""
+ "Content-Type: text/plain; charset=UTF-8\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n"
+-"X-Generator: Weblate 4.2.2\n"
++"X-Generator: Weblate 4.3.2\n"
+
+ #: ../src/daemon/cmdline.c:113
+ #, c-format
+@@ -95,11 +95,11 @@ msgstr ""
+
+ #: ../src/daemon/cmdline.c:246
+ msgid "--daemonize expects boolean argument"
+-msgstr ""
++msgstr "Voľba --daemonize očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:254
+ msgid "--fail expects boolean argument"
+-msgstr ""
++msgstr "Voľba --fail očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:265
+ msgid ""
+@@ -109,23 +109,23 @@ msgstr ""
+
+ #: ../src/daemon/cmdline.c:277
+ msgid "--high-priority expects boolean argument"
+-msgstr ""
++msgstr "Voľba --high-priority očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:285
+ msgid "--realtime expects boolean argument"
+-msgstr ""
++msgstr "Voľba --realtime očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:293
+ msgid "--disallow-module-loading expects boolean argument"
+-msgstr ""
++msgstr "Voľba --disallow-module-loading očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:301
+ msgid "--disallow-exit expects boolean argument"
+-msgstr ""
++msgstr "Voľba --disallow-exit očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:309
+ msgid "--use-pid-file expects boolean argument"
+-msgstr ""
++msgstr "Voľba --use-pid-file očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:328
+ msgid ""
+@@ -141,11 +141,11 @@ msgstr ""
+
+ #: ../src/daemon/cmdline.c:338
+ msgid "--log-time expects boolean argument"
+-msgstr ""
++msgstr "Voľba --log-time očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:346
+ msgid "--log-meta expects boolean argument"
+-msgstr ""
++msgstr "Voľba --log-meta očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:366
+ #, c-format
+@@ -158,11 +158,11 @@ msgstr "Voľba --system očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:381
+ msgid "--no-cpu-limit expects boolean argument"
+-msgstr ""
++msgstr "Voľba --no-cpu-limit očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:389
+ msgid "--disable-shm expects boolean argument"
+-msgstr ""
++msgstr "Voľba --disable-shm očakáva booleovský parameter"
+
+ #: ../src/daemon/cmdline.c:397
+ msgid "--enable-memfd expects boolean argument"
+@@ -350,7 +350,7 @@ msgstr "Zlyhalo zmenenie UID: %s"
+
+ #: ../src/daemon/main.c:255
+ msgid "System wide mode unsupported on this platform."
+-msgstr ""
++msgstr "Systémový režim nie je podporovaný na tejto platforme."
+
+ #: ../src/daemon/main.c:484
+ msgid "Failed to parse command line."
+@@ -395,10 +395,12 @@ msgstr ""
+ #: ../src/daemon/main.c:712
+ msgid "Running in system mode, but --disallow-exit not set."
+ msgstr ""
++"Spustené v systémovom režime, ale voľba --disallow-exit nie je nastavená."
+
+ #: ../src/daemon/main.c:715
+ msgid "Running in system mode, but --disallow-module-loading not set."
+ msgstr ""
++"Spustené v systémovom režime, ale voľba --disallow-exit nie je nastavená."
+
+ #: ../src/daemon/main.c:718
+ msgid "Running in system mode, forcibly disabling SHM mode."
+@@ -407,10 +409,11 @@ msgstr ""
+ #: ../src/daemon/main.c:723
+ msgid "Running in system mode, forcibly disabling exit idle time."
+ msgstr ""
++"Spustené v systémovom režime. Ukončenie pri nečinnosti je vynútene zakázané."
+
+ #: ../src/daemon/main.c:756
+ msgid "Failed to acquire stdio."
+-msgstr ""
++msgstr "Zlyhalo získanie štandardného vstupu/výstupu."
+
+ #: ../src/daemon/main.c:762 ../src/daemon/main.c:833
+ #, c-format
+@@ -866,11 +869,11 @@ msgstr "Vstup cez Bluetooth"
+
+ #: ../src/modules/bluetooth/module-bluez5-device.c:1786
+ msgid "High Fidelity Playback (A2DP Sink)"
+-msgstr ""
++msgstr "HiFi prehrávanie (cieľ A2DP)"
+
+ #: ../src/modules/bluetooth/module-bluez5-device.c:1797
+ msgid "High Fidelity Capture (A2DP Source)"
+-msgstr ""
++msgstr "HiFi zaznamenávanie (zdroj A2DP)"
+
+ #: ../src/modules/bluetooth/module-bluez5-device.c:1808
+ msgid "Headset Head Unit (HSP/HFP)"
+@@ -926,7 +929,7 @@ msgstr ""
+
+ #: ../src/modules/module-ladspa-sink.c:51
+ msgid "Virtual LADSPA sink"
+-msgstr ""
++msgstr "Virtuálny cieľ LADSPA"
+
+ #: ../src/modules/module-ladspa-sink.c:55
+ msgid ""
+@@ -979,7 +982,7 @@ msgstr "Tunel do %s/%s"
+
+ #: ../src/modules/module-virtual-surround-sink.c:47
+ msgid "Virtual surround sink"
+-msgstr ""
++msgstr "Virtuálny priestorový cieľ"
+
+ #: ../src/modules/module-virtual-surround-sink.c:51
+ msgid ""
+@@ -1028,11 +1031,11 @@ msgstr "Basový reproduktor"
+
+ #: ../src/pulse/channelmap.c:115
+ msgid "Front Left-of-center"
+-msgstr ""
++msgstr "Predný ľavý stredový"
+
+ #: ../src/pulse/channelmap.c:116
+ msgid "Front Right-of-center"
+-msgstr ""
++msgstr "Predný pravý stredový"
+
+ #: ../src/pulse/channelmap.c:118
+ msgid "Side Left"
+@@ -1553,7 +1556,7 @@ msgstr ""
+ #: ../src/utils/pacat.c:416
+ #, c-format
+ msgid "Stream buffer attributes changed.%s"
+-msgstr ""
++msgstr "Atribúty zásobníka prúdu boli zmenené. %s"
+
+ #: ../src/utils/pacat.c:431
+ msgid "Cork request stack is empty: corking stream"
+@@ -1759,7 +1762,7 @@ msgstr "Neznámy formát súboru %s."
+
+ #: ../src/utils/pacat.c:995
+ msgid "Failed to parse the argument for --monitor-stream"
+-msgstr "Zlyhala analyzovanie argumentu pre --monitor-stream"
++msgstr "Zlyhala analyzovanie parametra pre voľbu --monitor-stream"
+
+ #: ../src/utils/pacat.c:1006
+ msgid "Invalid sample specification"
+@@ -1856,7 +1859,7 @@ msgstr ""
+
+ #: ../src/utils/pacmd.c:51 ../src/utils/pactl.c:1570
+ msgid "NAME [ARGS ...]"
+-msgstr "NÁZOV [ARGUMENTY ...]"
++msgstr "NÁZOV [PARAMETRE ...]"
+
+ #: ../src/utils/pacmd.c:52 ../src/utils/pacmd.c:60 ../src/utils/pactl.c:1571
+ msgid "NAME|#N"
+@@ -1897,15 +1900,15 @@ msgstr "Č."
+
+ #: ../src/utils/pacmd.c:62
+ msgid "NAME SINK|#N"
+-msgstr ""
++msgstr "NÁZOV CIEĽA|Č."
+
+ #: ../src/utils/pacmd.c:64 ../src/utils/pacmd.c:65
+ msgid "NAME FILENAME"
+-msgstr "NÁZOV NÁZOV-SÚBORU"
++msgstr "NÁZOV NÁZOV_SÚBORU"
+
+ #: ../src/utils/pacmd.c:66
+ msgid "PATHNAME"
+-msgstr "NÁZOV-CESTY"
++msgstr "NÁZOV_CESTY"
+
+ #: ../src/utils/pacmd.c:67
+ msgid "FILENAME SINK|#N"
+@@ -1913,7 +1916,7 @@ msgstr ""
+
+ #: ../src/utils/pacmd.c:69 ../src/utils/pactl.c:1572
+ msgid "#N SINK|SOURCE"
+-msgstr ""
++msgstr "Č. CIEĽU|ZDROJ"
+
+ #: ../src/utils/pacmd.c:71 ../src/utils/pacmd.c:77 ../src/utils/pacmd.c:78
+ msgid "1|0"
+@@ -1929,7 +1932,7 @@ msgstr "NÁZOV|Č. PORTU"
+
+ #: ../src/utils/pacmd.c:74 ../src/utils/pactl.c:1582
+ msgid "CARD-NAME|CARD-#N PORT OFFSET"
+-msgstr "NÁZOV-KARTY|KARTA-Č. PORT POSUNUTIE"
++msgstr "NÁZOV_KARTY|KARTA-Č. PORT POSUNUTIE"
+
+ #: ../src/utils/pacmd.c:75
+ msgid "TARGET"
+@@ -2053,11 +2056,20 @@ msgid ""
+ "Default Source: %s\n"
+ "Cookie: %04x:%04x\n"
+ msgstr ""
++"Používateľské meno: %s\n"
++"Názov hostiteľa: %s\n"
++"Názov servera: %s\n"
++"Verzia servera: %s\n"
++"Predvolená špecifikácia snímky: %s\n"
++"Predvolená mapa kanálov: %s\n"
++"Predvolený cieľ: %s\n"
++"Predvolený zdroj: %s\n"
++"Cookie: %04x:%04x\n"
+
+ #: ../src/utils/pactl.c:255 ../src/utils/pactl.c:900 ../src/utils/pactl.c:978
+ #, c-format
+ msgid "Failed to get sink information: %s"
+-msgstr ""
++msgstr "Zlyhalo získanie informácií o cieli: %s"
+
+ #: ../src/utils/pactl.c:281
+ #, c-format
+@@ -2148,7 +2160,7 @@ msgid ""
+ msgstr ""
+ "Modul #%u\n"
+ "\tNázov: %s\n"
+-"\tArgument: %s\n"
++"\tParameter: %s\n"
+ "\tPočítadlo použití: %s\n"
+ "\tVlastnosti:\n"
+ "\t\t%s\n"
+@@ -2203,7 +2215,7 @@ msgstr "\tProfily:\n"
+ #: ../src/utils/pactl.c:581
+ #, c-format
+ msgid "\t\t%s: %s (sinks: %u, sources: %u, priority: %u, available: %s)\n"
+-msgstr ""
++msgstr "\t\t%s: %s (ciele: %u, zdroje: %u, priorita: %u, dostupné: %s)\n"
+
+ #: ../src/utils/pactl.c:586
+ #, c-format
+@@ -2250,6 +2262,23 @@ msgid ""
+ "\tProperties:\n"
+ "\t\t%s\n"
+ msgstr ""
++"Vstup cieľa č. %u\n"
++"\tOvládač: %s\n"
++"\tModul vlastníka: %s\n"
++"\tKlient: %s\n"
++"\tCieľ: %u\n"
++"\tŠpecifikácia snímky: %s\n"
++"\tMapa kanálov: %s\n"
++"\tFormát: %s\n"
++"\tBlokovanie: %s\n"
++"\tStlmenie: %s\n"
++"\tHlasitosť: %s\n"
++"\t vyváženie %0.2f\n"
++"\tOneskorenie zásobníka: %0.0f usek\n"
++"\tOneskorenie cieľa: %0.0f usek\n"
++"\tMetóda presnímkovania: %s\n"
++"\tVlastnosti:\n"
++"\t\t%s\n"
+
+ #: ../src/utils/pactl.c:692 ../src/utils/pactl.c:960 ../src/utils/pactl.c:1023
+ #, c-format
+@@ -2308,7 +2337,7 @@ msgstr "Zlyhanie: %s"
+ #: ../src/utils/pactl.c:866
+ #, c-format
+ msgid "Failed to unload module: Module %s not loaded"
+-msgstr ""
++msgstr "Zlyhalo uvoľnenie modulu: Modul %s nie je načítaný"
+
+ #: ../src/utils/pactl.c:884
+ #, c-format
+@@ -2351,7 +2380,7 @@ msgstr "neznámy"
+
+ #: ../src/utils/pactl.c:1147
+ msgid "sink"
+-msgstr ""
++msgstr "cieľ"
+
+ #: ../src/utils/pactl.c:1150
+ msgid "source"
+@@ -2430,11 +2459,11 @@ msgstr "[TYP]"
+
+ #: ../src/utils/pactl.c:1567
+ msgid "FILENAME [NAME]"
+-msgstr "NÁZOV-SÚBORU [NÁZOV]"
++msgstr "NÁZOV_SÚBORU [NÁZOV]"
+
+ #: ../src/utils/pactl.c:1568
+ msgid "NAME [SINK]"
+-msgstr ""
++msgstr "NÁZOV [CIEĽ]"
+
+ #: ../src/utils/pactl.c:1577
+ msgid "NAME|#N VOLUME [VOLUME ...]"
+@@ -2527,11 +2556,11 @@ msgstr "Musíte určiť číslo výstupu zdroja a zdroj"
+
+ #: ../src/utils/pactl.c:1775
+ msgid "You have to specify a module name and arguments."
+-msgstr "Musíte určiť názov modulu a argumenty."
++msgstr "Musíte určiť názov modulu a parametre."
+
+ #: ../src/utils/pactl.c:1795
+ msgid "You have to specify a module index or name"
+-msgstr ""
++msgstr "Musíte určiť číslo modulu alebo názov"
+
+ #: ../src/utils/pactl.c:1808
+ msgid ""
+@@ -2558,7 +2587,7 @@ msgstr ""
+
+ #: ../src/utils/pactl.c:1867
+ msgid "You have to specify a sink name"
+-msgstr ""
++msgstr "Musíte určiť názov cieľa"
+
+ #: ../src/utils/pactl.c:1877
+ msgid "You have to specify a source name/index and a port name"
+@@ -2631,6 +2660,8 @@ msgid ""
+ "You have to specify a sink index and a semicolon-separated list of supported "
+ "formats"
+ msgstr ""
++"Musíte určiť číslo cieľa a bodkočiarkami oddelený zoznam podporovaných "
++"formátov"
+
+ #: ../src/utils/pactl.c:2038
+ msgid "You have to specify a card name/index, a port name and a latency offset"
+@@ -2755,7 +2786,7 @@ msgstr "Zdroj: %s\n"
+ #: ../src/utils/pax11publish.c:114
+ #, c-format
+ msgid "Sink: %s\n"
+-msgstr ""
++msgstr "Cieľ: %s\n"
+
+ #: ../src/utils/pax11publish.c:116
+ #, c-format
+diff --git a/shell-completion/bash/pulseaudio b/shell-completion/bash/pulseaudio
+index 08f247e81..6c2635f65 100644
+--- a/shell-completion/bash/pulseaudio
++++ b/shell-completion/bash/pulseaudio
+@@ -113,7 +113,7 @@ _pactl() {
+ local comps
+ local flags='-h --help --version -s --server= --client-name='
+ local list_types='short sinks sources sink-inputs source-outputs cards
+- modules samples clients'
++ modules samples clients message-handlers'
+ local commands=(stat info list exit upload-sample play-sample remove-sample
+ load-module unload-module move-sink-input move-source-output
+ suspend-sink suspend-source set-card-profile set-default-sink
+@@ -121,7 +121,7 @@ _pactl() {
+ set-source-volume set-sink-input-volume set-source-output-volume
+ set-sink-mute set-source-mute set-sink-input-mute
+ set-source-output-mute set-sink-formats set-port-latency-offset
+- subscribe help)
++ subscribe send-message help)
+
+ _init_completion -n = || return
+ preprev=${words[$cword-2]}
+@@ -271,7 +271,7 @@ _pacmd() {
+ move-sink-input move-source-output suspend-sink suspend-source
+ suspend set-card-profile set-sink-port set-source-port
+ set-port-latency-offset set-log-target set-log-level set-log-meta
+- set-log-time set-log-backtrace)
++ set-log-time set-log-backtrace send-message)
+ _init_completion -n = || return
+ preprev=${words[$cword-2]}
+
+diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio
+index da72010c6..2a1f18eaa 100644
+--- a/shell-completion/zsh/_pulseaudio
++++ b/shell-completion/zsh/_pulseaudio
+@@ -263,6 +263,7 @@ _pactl_completion() {
+ 'set-sink-input-mute: mute a stream'
+ 'set-source-output-mute: mute a recording stream'
+ 'set-sink-formats: set supported formats of a sink'
++ 'send-message: send a message to a pulseaudio object'
+ 'subscribe: subscribe to events'
+ )
+
+@@ -284,6 +285,7 @@ _pactl_completion() {
+ 'clients: list connected clients'
+ 'samples: list samples'
+ 'cards: list available cards'
++ 'message-handlers: list available message-handlers'
+ )
+
+ if ((CURRENT == 2)); then
+@@ -561,6 +563,7 @@ _pacmd_completion() {
+ 'dump: show daemon configuration'
+ 'dump-volumes: show the state of all volumes'
+ 'shared: show shared properties'
++ 'send-message: send a message to a pulseaudio object'
+ 'exit: ask the PulseAudio daemon to exit'
+ )
+ _describe 'pacmd commands' _pacmd_commands
+diff --git a/src/Makefile.am b/src/Makefile.am
+index bd764037b..97c9dce9f 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -206,8 +206,11 @@ bin_PROGRAMS += pasuspender
+ endif
+
+ if HAVE_AF_UNIX
++if !OS_IS_WIN32
++# pacmd relies on Unix signals, which are not present on Windows.
+ bin_PROGRAMS += pacmd
+ endif
++endif
+
+ if HAVE_X11
+ bin_PROGRAMS += pax11publish
+@@ -711,6 +714,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \
+ pulse/timeval.c pulse/timeval.h \
+ pulse/rtclock.c pulse/rtclock.h \
+ pulse/volume.c pulse/volume.h \
++ pulse/message-params.c pulse/message-params.h \
+ pulsecore/atomic.h \
+ pulsecore/authkey.c pulsecore/authkey.h \
+ pulsecore/conf-parser.c pulsecore/conf-parser.h \
+@@ -917,6 +921,7 @@ libpulse_la_SOURCES = \
+ pulse/mainloop-api.c pulse/mainloop-api.h \
+ pulse/mainloop-signal.c pulse/mainloop-signal.h \
+ pulse/mainloop.c pulse/mainloop.h \
++ pulse/message-params.c pulse/message-params.h \
+ pulse/operation.c pulse/operation.h \
+ pulse/proplist.c pulse/proplist.h \
+ pulse/pulseaudio.h \
+@@ -1268,7 +1273,6 @@ modlibexec_LTLIBRARIES += \
+ module-loopback.la \
+ module-virtual-sink.la \
+ module-virtual-source.la \
+- module-virtual-surround-sink.la \
+ module-switch-on-connect.la \
+ module-switch-on-port-available.la \
+ module-filter-apply.la \
+@@ -1367,7 +1371,10 @@ dist_alsaprofilesets_DATA = \
+ modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf \
+ modules/alsa/mixer/profile-sets/usb-gaming-headset.conf \
+ modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \
+- modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf
++ modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf \
++ modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf \
++ modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf \
++ modules/alsa/mixer/profile-sets/sennheiser-gsx.conf
+
+ if HAVE_UDEV
+ dist_udevrules_DATA = \
+@@ -1414,7 +1421,9 @@ dist_alsapaths_DATA = \
+ modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf \
+ modules/alsa/mixer/paths/usb-gaming-headset-input.conf \
+ modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf \
+- modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
++ modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf \
++ modules/alsa/mixer/paths/analog-output-chat.conf \
++ modules/alsa/mixer/paths/virtual-surround-7.1.conf
+
+ endif
+
+@@ -1528,6 +1537,11 @@ endif
+ endif
+ endif
+
++if HAVE_FFTW
++modlibexec_LTLIBRARIES += \
++ module-virtual-surround-sink.la
++endif
++
+ if HAVE_DBUS
+ if HAVE_FFTW
+ modlibexec_LTLIBRARIES += \
+@@ -1773,9 +1787,9 @@ module_virtual_source_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_virtual_source_la_LIBADD = $(MODULE_LIBADD)
+
+ module_virtual_surround_sink_la_SOURCES = modules/module-virtual-surround-sink.c
+-module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) -DPA_MODULE_NAME=module_virtual_surround_sink
++module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(FFTW_CFLAGS) -DPA_MODULE_NAME=module_virtual_surround_sink
+ module_virtual_surround_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
+-module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD)
++module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD) $(FFTW_LIBS)
+
+ # X11
+
+diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
+index bcf7329f1..ffef554be 100644
+--- a/src/daemon/daemon-conf.c
++++ b/src/daemon/daemon-conf.c
+@@ -665,9 +665,21 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
+ pa_xfree(c->config_file);
+ c->config_file = NULL;
+
++ const char *default_config_file = DEFAULT_CONFIG_FILE;
++#ifdef HAVE_RUNNING_FROM_BUILD_TREE
++ if (pa_run_from_build_tree()) {
++ pa_log_notice("Detected that we are run from the build tree, fixing default daemon.conf file path.");
++#ifdef MESON_BUILD
++ default_config_file = PA_BUILDDIR PA_PATH_SEP "src" PA_PATH_SEP "daemon" PA_PATH_SEP "daemon.conf";
++#else
++ default_config_file = PA_BUILDDIR PA_PATH_SEP "daemon.conf";
++#endif // Endof #ifdef MESON_BUILD
++ }
++#endif // Endof #ifdef HAVE_RUNNING_FROM_BUILD_TREE
++
+ f = filename ?
+ pa_fopen_cloexec(c->config_file = pa_xstrdup(filename), "r") :
+- pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
++ pa_open_config_file(default_config_file, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
+
+ if (!f && errno != ENOENT) {
+ pa_log_warn(_("Failed to open configuration file: %s"), pa_cstrerror(errno));
+diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
+index 030334f36..3468a89b9 100755
+--- a/src/daemon/default.pa.in
++++ b/src/daemon/default.pa.in
+@@ -173,3 +173,8 @@ load-module module-filter-apply
+ ### Make some devices default
+ #set-default-sink output
+ #set-default-source input
++
++### Allow including a default.pa.d directory, which if present, can be used
++### for additional configuration snippets.
++.nofail
++.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/default.pa.d
+diff --git a/src/daemon/main.c b/src/daemon/main.c
+index 59f931219..1032390ca 100644
+--- a/src/daemon/main.c
++++ b/src/daemon/main.c
+@@ -101,7 +101,7 @@
+ #ifdef DISABLE_LIBTOOL_PRELOAD
+ /* FIXME: work around a libtool bug by making sure we have 2 elements. Bug has
+ * been reported: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29576 */
+-const lt_dlsymlist lt_preloaded_symbols[] = {
++LT_DLSYM_CONST lt_dlsymlist lt_preloaded_symbols[] = {
+ { "@PROGRAM@", NULL },
+ { NULL, NULL }
+ };
+@@ -480,7 +480,13 @@ int main(int argc, char *argv[]) {
+ pa_unblock_sigs(-1);
+ pa_reset_priority();
+
++ /* Load locale from the environment. */
+ setlocale(LC_ALL, "");
++
++ /* Set LC_NUMERIC to C so that floating point strings are consistently
++ * formatted and parsed across locales. */
++ setlocale(LC_NUMERIC, "C");
++
+ pa_init_i18n();
+
+ conf = pa_daemon_conf_new();
+diff --git a/src/daemon/meson.build b/src/daemon/meson.build
+index 9c9f807e7..4a57d7baa 100644
+--- a/src/daemon/meson.build
++++ b/src/daemon/meson.build
+@@ -31,7 +31,7 @@ executable('pulseaudio',
+ include_directories : [configinc, topinc],
+ link_args : ['-ffast-math'],
+ link_with : [libpulsecore, libpulsecommon, libpulse],
+- dependencies : [ltdl_dep, cap_dep, dbus_dep, libsystemd_dep, dl_dep, libintl_dep],
++ dependencies : [ltdl_dep, cap_dep, dbus_dep, libsystemd_dep, dl_dep, libintl_dep, platform_dep, platform_socket_dep],
+ c_args : pa_c_args,
+ )
+
+diff --git a/src/daemon/system.pa.in b/src/daemon/system.pa.in
+index 73e39ec93..1470e2368 100755
+--- a/src/daemon/system.pa.in
++++ b/src/daemon/system.pa.in
+@@ -60,3 +60,8 @@ load-module module-suspend-on-idle
+
+ ### Enable positioned event sounds
+ load-module module-position-event-sounds
++
++### Allow including a system.pa.d directory, which if present, can be used
++### for additional configuration snippets.
++.nofail
++.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/system.pa.d
+diff --git a/src/map-file b/src/map-file
+index b0cd1bfc4..7b1ed5206 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -87,6 +87,7 @@ pa_context_remove_autoload_by_name;
+ pa_context_remove_sample;
+ pa_context_rttime_new;
+ pa_context_rttime_restart;
++pa_context_send_message_to_object;
+ pa_context_set_card_profile_by_index;
+ pa_context_set_card_profile_by_name;
+ pa_context_set_default_sink;
+@@ -228,6 +229,27 @@ pa_mainloop_quit;
+ pa_mainloop_run;
+ pa_mainloop_set_poll_func;
+ pa_mainloop_wakeup;
++pa_message_params_begin_list;
++pa_message_params_end_list;
++pa_message_params_free;
++pa_message_params_new;
++pa_message_params_read_bool;
++pa_message_params_read_double;
++pa_message_params_read_double_array;
++pa_message_params_read_int64;
++pa_message_params_read_int64_array;
++pa_message_params_read_raw;
++pa_message_params_read_string;
++pa_message_params_read_string_array;
++pa_message_params_read_uint64;
++pa_message_params_read_uint64_array;
++pa_message_params_to_string_free;
++pa_message_params_write_bool;
++pa_message_params_write_double;
++pa_message_params_write_int64;
++pa_message_params_write_raw;
++pa_message_params_write_string;
++pa_message_params_write_uint64;
+ pa_msleep;
+ pa_thread_make_realtime;
+ pa_operation_cancel;
+diff --git a/src/meson.build b/src/meson.build
+index 8d74a3164..0842db297 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -5,6 +5,7 @@ libpulsecommon_sources = [
+ 'pulse/format.c',
+ 'pulse/json.c',
+ 'pulse/mainloop-api.c',
++ 'pulse/message-params.c',
+ 'pulse/xmalloc.c',
+ 'pulse/proplist.c',
+ 'pulse/utf8.c',
+@@ -38,7 +39,6 @@ libpulsecommon_sources = [
+ 'pulsecore/memblock.c',
+ 'pulsecore/memblockq.c',
+ 'pulsecore/memchunk.c',
+- 'pulsecore/mutex-posix.c',
+ 'pulsecore/native-common.c',
+ 'pulsecore/once.c',
+ 'pulsecore/packet.c',
+@@ -55,7 +55,6 @@ libpulsecommon_sources = [
+ 'pulsecore/random.c',
+ 'pulsecore/srbchannel.c',
+ 'pulsecore/sample-util.c',
+- 'pulsecore/semaphore-posix.c',
+ 'pulsecore/shm.c',
+ 'pulsecore/bitset.c',
+ 'pulsecore/socket-client.c',
+@@ -64,7 +63,6 @@ libpulsecommon_sources = [
+ 'pulsecore/strbuf.c',
+ 'pulsecore/strlist.c',
+ 'pulsecore/tagstruct.c',
+- 'pulsecore/thread-posix.c',
+ 'pulsecore/time-smoother.c',
+ 'pulsecore/tokenizer.c',
+ 'pulsecore/usergroup.c',
+@@ -78,6 +76,7 @@ libpulsecommon_headers = [
+ 'pulse/format.h',
+ 'pulse/json.h',
+ 'pulse/mainloop-api.h',
++ 'pulse/message-params.h',
+ 'pulse/xmalloc.h',
+ 'pulse/proplist.h',
+ 'pulse/utf8.h',
+@@ -175,6 +174,20 @@ if x11_dep.found()
+ endif
+
+ # FIXME: Do non-POSIX thread things
++if host_machine.system() == 'windows'
++ libpulsecommon_sources += [
++ 'pulsecore/mutex-win32.c',
++ 'pulsecore/poll-win32.c',
++ 'pulsecore/semaphore-win32.c',
++ 'pulsecore/thread-win32.c',
++ ]
++else
++ libpulsecommon_sources += [
++ 'pulsecore/mutex-posix.c',
++ 'pulsecore/semaphore-posix.c',
++ 'pulsecore/thread-posix.c'
++ ]
++endif
+ # FIXME: Do SIMD things
+
+ libpulsecommon = shared_library('pulsecommon-' + pa_version_major_minor,
+@@ -188,6 +201,7 @@ libpulsecommon = shared_library('pulsecommon-' + pa_version_major_minor,
+ dependencies : [
+ libm_dep, thread_dep, dl_dep, shm_dep, iconv_dep, sndfile_dep, dbus_dep,
+ x11_dep, libsystemd_dep, glib_dep, gtk_dep, asyncns_dep, libintl_dep,
++ platform_dep, platform_socket_dep,
+ ],
+ implicit_include_directories : false)
+
+diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules
+index 7bfacda09..cc5a3597a 100644
+--- a/src/modules/alsa/90-pulseaudio.rules
++++ b/src/modules/alsa/90-pulseaudio.rules
+@@ -110,6 +110,9 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi
+ ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
+ ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf"
+ ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
++ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{PULSE_PROFILE_SET}="behringer-umc22.conf"
++ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0269", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-120w-g2.conf"
++ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0567", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-audio-module.conf"
+
+ # ID 1038:12ad is for the 2018 refresh of the Arctis 7.
+ # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration).
+@@ -144,6 +147,16 @@ ATTRS{idVendor}=="0951", ATTRS{idProduct}=="16ff", ENV{ID_ID}="usb-HyperX_Cloud_
+ ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1702", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_Hi-Res_2Ch-$env{ID_USB_INTERFACE_NUM}"
+ ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1703", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_3D_8Ch-$env{ID_USB_INTERFACE_NUM}"
+
++# OnePlus Type-C Bullets (ED117)
++ATTRS{idVendor}=="2a70", ATTRS{idProduct}=="1881", ENV{PULSE_PROFILE_SET}="simple-headphones-mic.conf"
++
++# ID 1395:005e is for Sennheiser GSX 1000
++# ID 1395:00a0 is for Sennheiser GSX 1000
++# ID 1395:005f is for Sennheiser GSX 1200
++ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005e", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
++ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00a0", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
++ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005f", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
++
+ GOTO="pulseaudio_end"
+
+ LABEL="pulseaudio_check_pci"
+diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
+index 063179052..a6e6672ad 100644
+--- a/src/modules/alsa/alsa-mixer.c
++++ b/src/modules/alsa/alsa-mixer.c
+@@ -113,7 +113,7 @@ struct description2_map {
+ pa_device_port_type_t type;
+ };
+
+-static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) {
++char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id) {
+ if (id->index > 0) {
+ snprintf(dst, dst_len, "'%s',%d", id->name, id->index);
+ } else {
+@@ -153,7 +153,7 @@ static int alsa_id_decode(const char *src, char *name, int *index) {
+ return 0;
+ }
+
+-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) {
++pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index) {
+ pa_alsa_jack *jack;
+
+ pa_assert(name);
+@@ -162,7 +162,8 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name
+ jack->path = path;
+ jack->mixer_device_name = pa_xstrdup(mixer_device_name);
+ jack->name = pa_xstrdup(name);
+- jack->alsa_name = pa_sprintf_malloc("%s Jack", name);
++ jack->alsa_id.name = pa_sprintf_malloc("%s Jack", name);
++ jack->alsa_id.index = index;
+ jack->state_unplugged = PA_AVAILABLE_NO;
+ jack->state_plugged = PA_AVAILABLE_YES;
+ jack->ucm_devices = pa_dynarray_new(NULL);
+@@ -177,7 +178,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) {
+ pa_dynarray_free(jack->ucm_hw_mute_devices);
+ pa_dynarray_free(jack->ucm_devices);
+
+- pa_xfree(jack->alsa_name);
++ pa_xfree(jack->alsa_id.name);
+ pa_xfree(jack->name);
+ pa_xfree(jack->mixer_device_name);
+ pa_xfree(jack);
+@@ -689,6 +690,20 @@ static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_M
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
+ };
+
++static snd_mixer_selem_channel_id_t alsa_channel_positions[POSITION_MASK_CHANNELS] = {
++ SND_MIXER_SCHN_FRONT_LEFT,
++ SND_MIXER_SCHN_FRONT_RIGHT,
++ SND_MIXER_SCHN_REAR_LEFT,
++ SND_MIXER_SCHN_REAR_RIGHT,
++ SND_MIXER_SCHN_FRONT_CENTER,
++ SND_MIXER_SCHN_WOOFER,
++ SND_MIXER_SCHN_SIDE_LEFT,
++ SND_MIXER_SCHN_SIDE_RIGHT,
++#if POSITION_MASK_CHANNELS > 8
++#error "Extend alsa_channel_positions[] array (9+)"
++#endif
++};
++
+ static void setting_free(pa_alsa_setting *s) {
+ pa_assert(s);
+
+@@ -821,7 +836,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -847,14 +862,14 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", buf, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", buf, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+@@ -877,14 +892,14 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", buf, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", buf, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+@@ -992,7 +1007,7 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) {
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -1158,7 +1173,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -1350,7 +1365,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -1361,7 +1376,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
+ r = snd_mixer_selem_set_capture_switch_all(me, b);
+
+ if (r < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+ }
+
+@@ -1405,7 +1420,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -1450,7 +1465,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
+ }
+
+ if (r < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno));
+ }
+
+@@ -1656,19 +1671,19 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
+
+ if (r < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r));
+ return false;
+ }
+
+ if (e->min_volume >= e->max_volume) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.",
+ buf, e->min_volume, e->max_volume);
+ return false;
+ }
+ if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
+ e->constant_volume, buf, e->min_volume, e->max_volume);
+ return false;
+@@ -1676,7 +1691,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+
+
+ if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
+ "real hardware range (%li-%li). Disabling the decibel fix.", buf,
+ e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume);
+@@ -1703,19 +1718,19 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ long max_dB_checked = 0;
+
+ if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume);
+ return false;
+ }
+
+ if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume);
+ return false;
+ }
+
+ if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
+ "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
+ "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0,
+@@ -1738,7 +1753,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+
+ if (e->volume_limit >= 0) {
+ if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
+ "%li-%li. The volume limit is ignored.",
+ buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
+@@ -1750,7 +1765,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ e->db_fix->max_step = e->max_volume;
+ e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
+ } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r));
+ e->has_dB = false;
+ } else
+@@ -1767,7 +1782,11 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ if (is_mono) {
+ e->n_channels = 1;
+
+- if (!e->override_map) {
++ if ((e->override_map & (1 << (e->n_channels-1))) && e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] == 0) {
++ pa_log_warn("Override map for mono element %s is invalid, ignoring override map", e->path->name);
++ e->override_map &= ~(1 << (e->n_channels-1));
++ }
++ if (!(e->override_map & (1 << (e->n_channels-1)))) {
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+ if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
+ continue;
+@@ -1791,27 +1810,28 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ }
+
+ if (e->n_channels <= 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Volume element %s with no channels?", buf);
+ return false;
+- } else if (e->n_channels > 2) {
++ } else if (e->n_channels > POSITION_MASK_CHANNELS) {
+ /* FIXME: In some places code like this is used:
+ *
+ * e->masks[alsa_channel_ids[p]][e->n_channels-1]
+ *
+ * The definition of e->masks is
+ *
+- * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
++ * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
+ *
+- * Since the array size is fixed at 2, we obviously
+- * don't support elements with more than two
++ * Since the array size is fixed at POSITION_MASK_CHANNELS, we obviously
++ * don't support elements with more than POSITION_MASK_CHANNELS
+ * channels... */
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels);
+ return false;
+ }
+
+- if (!e->override_map) {
++retry:
++ if (!(e->override_map & (1 << (e->n_channels-1)))) {
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+ bool has_channel;
+
+@@ -1834,6 +1854,17 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
+
+ e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
+ }
++
++ if (e->merged_mask == 0) {
++ if (!(e->override_map & (1 << (e->n_channels-1)))) {
++ pa_log_warn("Channel map for element %s is invalid", e->path->name);
++ return false;
++ }
++ pa_log_warn("Override map for element %s has empty result, ignoring override map", e->path->name);
++ e->override_map &= ~(1 << (e->n_channels-1));
++ goto retry;
++ }
++
+ return true;
+ }
+
+@@ -1943,12 +1974,12 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m)
+ }
+
+ new_name = pa_sprintf_malloc("%s,pcm=%i Jack", j->name, mapping->hw_device_index);
+- pa_xfree(j->alsa_name);
+- j->alsa_name = new_name;
++ pa_xfree(j->alsa_id.name);
++ j->alsa_id.name = new_name;
+ j->append_pcm_to_name = false;
+ }
+
+- has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL;
++ has_control = pa_alsa_mixer_find_card(m, &j->alsa_id, 0) != NULL;
+ pa_alsa_jack_set_has_control(j, has_control);
+
+ if (j->has_control) {
+@@ -2011,19 +2042,26 @@ finish:
+
+ static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
+ pa_alsa_jack *j;
++ char *name;
++ int index;
+
+ if (!pa_startswith(section, "Jack "))
+ return NULL;
+ section += 5;
+
+- if (p->last_jack && pa_streq(p->last_jack->name, section))
++ name = alloca(strlen(section) + 1);
++ if (alsa_id_decode(section, name, &index))
++ return NULL;
++
++ if (p->last_jack && pa_streq(p->last_jack->name, name) &&
++ p->last_jack->alsa_id.index == index)
+ return p->last_jack;
+
+ PA_LLIST_FOREACH(j, p->jacks)
+- if (pa_streq(j->name, section))
++ if (pa_streq(j->name, name) && j->alsa_id.index == index)
+ goto finish;
+
+- j = pa_alsa_jack_new(p, NULL, section);
++ j = pa_alsa_jack_new(p, NULL, name, index);
+ PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
+
+ finish:
+@@ -2427,6 +2465,16 @@ static int element_parse_volume_limit(pa_config_parser_state *state) {
+ return 0;
+ }
+
++static unsigned int parse_channel_position(const char *m)
++{
++ pa_channel_position_t p;
++
++ if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
++ return SND_MIXER_SCHN_UNKNOWN;
++
++ return alsa_channel_ids[p];
++}
++
+ static pa_channel_position_mask_t parse_mask(const char *m) {
+ pa_channel_position_mask_t v;
+
+@@ -2464,7 +2512,9 @@ static int element_parse_override_map(pa_config_parser_state *state) {
+ pa_alsa_path *p;
+ pa_alsa_element *e;
+ const char *split_state = NULL;
++ char *s;
+ unsigned i = 0;
++ int channel_count = 0;
+ char *n;
+
+ pa_assert(state);
+@@ -2476,31 +2526,60 @@ static int element_parse_override_map(pa_config_parser_state *state) {
+ return -1;
+ }
+
++ s = strstr(state->lvalue, ".");
++ if (s) {
++ pa_atoi(s + 1, &channel_count);
++ if (channel_count < 1 || channel_count > POSITION_MASK_CHANNELS) {
++ pa_log("[%s:%u] Override map index '%s' invalid in '%s'", state->filename, state->lineno, state->lvalue, state->section);
++ return 0;
++ }
++ } else {
++ pa_log("[%s:%u] Invalid override map syntax '%s' in '%s'", state->filename, state->lineno, state->lvalue, state->section);
++ return -1;
++ }
++
+ while ((n = pa_split(state->rvalue, ",", &split_state))) {
+ pa_channel_position_mask_t m;
++ snd_mixer_selem_channel_id_t channel_position;
++
++ if (i >= (unsigned)channel_count) {
++ pa_log("[%s:%u] Invalid override map size (>%d) in '%s'", state->filename, state->lineno, channel_count, state->section);
++ return -1;
++ }
++ channel_position = alsa_channel_positions[i];
+
+ if (!*n)
+ m = 0;
+ else {
+- if ((m = parse_mask(n)) == 0) {
+- pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, n, state->section);
++ s = strstr(n, ":");
++ if (s) {
++ *s = '\0';
++ s++;
++ channel_position = parse_channel_position(n);
++ if (channel_position == SND_MIXER_SCHN_UNKNOWN) {
++ pa_log("[%s:%u] Override map position '%s' invalid in '%s'", state->filename, state->lineno, n, state->section);
++ pa_xfree(n);
++ return -1;
++ }
++ }
++ if ((m = parse_mask(s ? s : n)) == 0) {
++ pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, s ? s : n, state->section);
+ pa_xfree(n);
+ return -1;
+ }
+ }
+
+- if (pa_streq(state->lvalue, "override-map.1"))
+- e->masks[i++][0] = m;
+- else
+- e->masks[i++][1] = m;
+-
+- /* Later on we might add override-map.3 and so on here ... */
+-
++ if (e->masks[channel_position][channel_count-1]) {
++ pa_log("[%s:%u] Override map '%s' duplicate position '%s' in '%s'", state->filename, state->lineno, s ? s : n, snd_mixer_selem_channel_name(channel_position), state->section);
++ pa_xfree(n);
++ return -1;
++ }
++ e->override_map |= (1 << (channel_count - 1));
++ e->masks[channel_position][channel_count-1] = m;
+ pa_xfree(n);
++ i++;
+ }
+
+- e->override_map = true;
+-
+ return 0;
+ }
+
+@@ -2574,7 +2653,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return -1;
+ }
+@@ -2587,7 +2666,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
+ r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
+
+ if (r < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+ }
+
+@@ -2595,7 +2674,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
+ pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
+
+ if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno));
+ }
+ }
+@@ -2652,7 +2731,7 @@ static int option_verify(pa_alsa_option *o) {
+
+ if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
+ o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
+- alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id);
+ pa_log("Element %s of option %s not set for select.", buf, o->name);
+ return -1;
+ }
+@@ -2660,7 +2739,7 @@ static int option_verify(pa_alsa_option *o) {
+ if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
+ !pa_streq(o->alsa_name, "on") &&
+ !pa_streq(o->alsa_name, "off")) {
+- alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id);
+ pa_log("Switch %s options need be named off or on ", buf);
+ return -1;
+ }
+@@ -2686,13 +2765,13 @@ static int element_verify(pa_alsa_element *e) {
+ (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
+ (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
+ (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log("Element %s cannot be required and absent at the same time.", buf);
+ return -1;
+ }
+
+ if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log("Element %s cannot set select for both switch and enumeration.", buf);
+ return -1;
+ }
+@@ -2718,6 +2797,7 @@ static int path_verify(pa_alsa_path *p) {
+ { "analog-input-video", N_("Video"), PA_DEVICE_PORT_TYPE_VIDEO },
+ { "analog-output", N_("Analog Output"), PA_DEVICE_PORT_TYPE_ANALOG },
+ { "analog-output-headphones", N_("Headphones"), PA_DEVICE_PORT_TYPE_HEADPHONES },
++ { "analog-output-headphones-2", N_("Headphones 2"), PA_DEVICE_PORT_TYPE_HEADPHONES },
+ { "analog-output-headphones-mono", N_("Headphones Mono Output"), PA_DEVICE_PORT_TYPE_HEADPHONES },
+ { "analog-output-lineout", N_("Line Out"), PA_DEVICE_PORT_TYPE_LINE },
+ { "analog-output-mono", N_("Analog Mono Output"), PA_DEVICE_PORT_TYPE_ANALOG },
+@@ -2729,6 +2809,9 @@ static int path_verify(pa_alsa_path *p) {
+ { "multichannel-output", N_("Multichannel Output"), PA_DEVICE_PORT_TYPE_LINE },
+ { "steelseries-arctis-output-game-common", N_("Game Output"), PA_DEVICE_PORT_TYPE_HEADSET },
+ { "steelseries-arctis-output-chat-common", N_("Chat Output"), PA_DEVICE_PORT_TYPE_HEADSET },
++ { "analog-chat-output", N_("Chat Output"), PA_DEVICE_PORT_TYPE_HEADSET },
++ { "analog-chat-input", N_("Chat Input"), PA_DEVICE_PORT_TYPE_HEADSET },
++ { "virtual-surround-7.1", N_("Virtual Surround 7.1"), PA_DEVICE_PORT_TYPE_HEADPHONES },
+ };
+
+ pa_alsa_element *e;
+@@ -2760,13 +2843,66 @@ static int path_verify(pa_alsa_path *p) {
+ return 0;
+ }
+
+-static const char *get_default_paths_dir(void) {
++static char *get_path_config_path(const char *paths_dir, const char *fname) {
++ char *path_config_path;
++ char *dir;
++ char *data_home;
++ pa_dynarray *data_dirs;
++
++ if (paths_dir) {
++ path_config_path = pa_maybe_prefix_path(fname, paths_dir);
++ if (access(path_config_path, R_OK) == 0)
++ return path_config_path;
++ else
++ pa_xfree(path_config_path);
++ }
++
+ #ifdef HAVE_RUNNING_FROM_BUILD_TREE
+- if (pa_run_from_build_tree())
+- return PA_SRCDIR "/modules/alsa/mixer/paths/";
+- else
++ if (pa_run_from_build_tree()) {
++ path_config_path = pa_maybe_prefix_path(fname, PA_SRCDIR "/modules/alsa/mixer/paths/");
++ if (access(path_config_path, R_OK) == 0)
++ return path_config_path;
++ else
++ pa_xfree(path_config_path);
++ }
+ #endif
+- return PA_ALSA_PATHS_DIR;
++
++ if (pa_get_data_home_dir(&data_home) == 0) {
++ dir = pa_sprintf_malloc("%s" PA_PATH_SEP "alsa-mixer" PA_PATH_SEP "paths", data_home);
++ pa_xfree(data_home);
++
++ path_config_path = pa_maybe_prefix_path(fname, dir);
++ pa_xfree(dir);
++
++ if (access(path_config_path, R_OK) == 0)
++ return path_config_path;
++ else
++ pa_xfree(path_config_path);
++ }
++
++ if (pa_get_data_dirs(&data_dirs) == 0) {
++ int idx;
++ const char *n;
++
++ PA_DYNARRAY_FOREACH(n, data_dirs, idx) {
++ dir = pa_sprintf_malloc("%s" PA_PATH_SEP "alsa-mixer" PA_PATH_SEP "paths", n);
++ path_config_path = pa_maybe_prefix_path(fname, dir);
++ pa_xfree(dir);
++
++ if (access(path_config_path, R_OK) == 0) {
++ pa_dynarray_free(data_dirs);
++ return path_config_path;
++ }
++ else {
++ pa_xfree(path_config_path);
++ }
++ }
++
++ pa_dynarray_free(data_dirs);
++ }
++
++ path_config_path = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR);
++ return path_config_path;
+ }
+
+ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) {
+@@ -2800,6 +2936,15 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa
+ { "enumeration", element_parse_enumeration, NULL, NULL },
+ { "override-map.1", element_parse_override_map, NULL, NULL },
+ { "override-map.2", element_parse_override_map, NULL, NULL },
++ { "override-map.3", element_parse_override_map, NULL, NULL },
++ { "override-map.4", element_parse_override_map, NULL, NULL },
++ { "override-map.5", element_parse_override_map, NULL, NULL },
++ { "override-map.6", element_parse_override_map, NULL, NULL },
++ { "override-map.7", element_parse_override_map, NULL, NULL },
++ { "override-map.8", element_parse_override_map, NULL, NULL },
++#if POSITION_MASK_CHANNELS > 8
++#error "Add override-map.9+ definitions"
++#endif
+ /* ... later on we might add override-map.3 and so on here ... */
+ { "required", element_parse_required, NULL, NULL },
+ { "required-any", element_parse_required, NULL, NULL },
+@@ -2824,10 +2969,9 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa
+ items[2].data = &p->description;
+ items[3].data = &mute_during_activation;
+
+- if (!paths_dir)
+- paths_dir = get_default_paths_dir();
++ fn = get_path_config_path(paths_dir, fname);
+
+- fn = pa_maybe_prefix_path(fname, paths_dir);
++ pa_log_info("Loading path config: %s", fn);
+
+ r = pa_config_parse(fn, NULL, items, p->proplist, false, p);
+ pa_xfree(fn);
+@@ -3017,6 +3161,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
+ double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
+ pa_channel_position_t t;
+ pa_channel_position_mask_t path_volume_channels = 0;
++ bool min_dB_set, max_dB_set;
+ char buf[64];
+
+ pa_assert(p);
+@@ -3032,22 +3177,23 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
+ pa_log_debug("Probing path '%s'", p->name);
+
+ PA_LLIST_FOREACH(j, p->jacks) {
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &j->alsa_id);
+ if (jack_probe(j, mapping, m) < 0) {
+ p->supported = false;
+- pa_log_debug("Probe of jack '%s' failed.", j->alsa_name);
++ pa_log_debug("Probe of jack %s failed.", buf);
+ return -1;
+ }
+- pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found");
++ pa_log_debug("Probe of jack %s succeeded (%s)", buf, j->has_control ? "found!" : "not found");
+ }
+
+ PA_LLIST_FOREACH(e, p->elements) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ if (element_probe(e, m) < 0) {
+ p->supported = false;
+ pa_log_debug("Probe of element %s failed.", buf);
+ return -1;
+ }
+- pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use);
++ pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d, has_dB=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use, e->has_dB);
+
+ if (ignore_dB)
+ e->has_dB = false;
+@@ -3111,18 +3257,30 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
+ p->supported = true;
+
+ p->min_dB = INFINITY;
++ min_dB_set = false;
+ p->max_dB = -INFINITY;
++ max_dB_set = false;
+
+ for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
+ if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
+- if (p->min_dB > min_dB[t])
++ if (p->min_dB > min_dB[t]) {
+ p->min_dB = min_dB[t];
++ min_dB_set = true;
++ }
+
+- if (p->max_dB < max_dB[t])
++ if (p->max_dB < max_dB[t]) {
+ p->max_dB = max_dB[t];
++ max_dB_set = true;
++ }
+ }
+ }
+
++ /* this is probably a wrong prediction, but it should be safe */
++ if (!min_dB_set)
++ p->min_dB = -INFINITY;
++ if (!max_dB_set)
++ p->max_dB = 0;
++
+ return 0;
+ }
+
+@@ -3138,7 +3296,7 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) {
+ void pa_alsa_jack_dump(pa_alsa_jack *j) {
+ pa_assert(j);
+
+- pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable");
++ pa_log_debug("Jack %s, alsa_name='%s', index='%d', detection %s", j->name, j->alsa_id.name, j->alsa_id.index, j->has_control ? "possible" : "unavailable");
+ }
+
+ void pa_alsa_option_dump(pa_alsa_option *o) {
+@@ -3158,8 +3316,8 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
+ pa_alsa_option *o;
+ pa_assert(e);
+
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+- pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
++ pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%02x",
+ buf,
+ e->direction,
+ e->switch_use,
+@@ -3171,7 +3329,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
+ e->required_absent,
+ (long long unsigned) e->merged_mask,
+ e->n_channels,
+- pa_yes_no(e->override_map));
++ e->override_map);
+
+ PA_LLIST_FOREACH(o, e->options)
+ pa_alsa_option_dump(o);
+@@ -3218,7 +3376,7 @@ static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_e
+
+ SELEM_INIT(sid, &e->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &e->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return;
+ }
+@@ -3513,7 +3671,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
+
+ SELEM_INIT(sid, &a->alsa_id);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+- alsa_id_str(buf, sizeof(buf), &a->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id);
+ pa_log_warn("Element %s seems to have disappeared.", buf);
+ return false;
+ }
+@@ -3544,7 +3702,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
+ return false;
+ for (s = 0; s <= SND_MIXER_SCHN_LAST; s++)
+ if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) {
+- alsa_id_str(buf, sizeof(buf), &a->alsa_id);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id);
+ pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d",
+ buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
+ return false;
+@@ -3621,7 +3779,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
+ continue;
+
+ PA_LLIST_FOREACH(jb, p2->jacks) {
+- if (jb->has_control && pa_streq(jb->alsa_name, ja->alsa_name) &&
++ if (jb->has_control && pa_streq(ja->alsa_id.name, jb->alsa_id.name) &&
++ (ja->alsa_id.index == jb->alsa_id.index) &&
+ (ja->state_plugged == jb->state_plugged) &&
+ (ja->state_unplugged == jb->state_unplugged)) {
+ exists = true;
+@@ -4311,7 +4470,8 @@ static void profile_set_set_availability_groups(pa_alsa_profile_set *ps) {
+ PA_LLIST_FOREACH(j2, p2->jacks) {
+ if (!j2->has_control || j2->state_plugged == PA_AVAILABLE_NO)
+ continue;
+- if (pa_streq(j->alsa_name, j2->alsa_name)) {
++ if (pa_streq(j->alsa_id.name, j2->alsa_id.name) &&
++ j->alsa_id.index == j2->alsa_id.index) {
+ j->state_plugged = PA_AVAILABLE_UNKNOWN;
+ j2->state_plugged = PA_AVAILABLE_UNKNOWN;
+ found = p2->availability_group;
+@@ -4392,6 +4552,8 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
+
+ static const struct description_map well_known_descriptions[] = {
+ { "analog-mono", N_("Analog Mono") },
++ { "analog-mono-left", N_("Analog Mono (Left)") },
++ { "analog-mono-right", N_("Analog Mono (Right)") },
+ { "analog-stereo", N_("Analog Stereo") },
+ { "mono-fallback", N_("Mono") },
+ { "stereo-fallback", N_("Stereo") },
+@@ -4402,6 +4564,8 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
+ * multichannel-input and multichannel-output. */
+ { "analog-stereo-input", N_("Analog Stereo") },
+ { "analog-stereo-output", N_("Analog Stereo") },
++ { "analog-stereo-headset", N_("Headset") },
++ { "analog-stereo-speakerphone", N_("Speakerphone") },
+ { "multichannel-input", N_("Multichannel") },
+ { "multichannel-output", N_("Multichannel") },
+ { "analog-surround-21", N_("Analog Surround 2.1") },
+@@ -4558,9 +4722,12 @@ static int profile_verify(pa_alsa_profile *p) {
+ static const struct description_map well_known_descriptions[] = {
+ { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") },
+ { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") },
++ { "output:analog-stereo-headset+input:analog-stereo-headset", N_("Headset") },
++ { "output:analog-stereo-speakerphone+input:analog-stereo-speakerphone", N_("Speakerphone") },
+ { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") },
+ { "output:multichannel-output+input:multichannel-input", N_("Multichannel Duplex") },
+ { "output:unknown-stereo+input:unknown-stereo", N_("Stereo Duplex") },
++ { "output:analog-output-surround71+output:analog-output-chat+input:analog-input", N_("Mono Chat + 7.1 Surround") },
+ { "off", N_("Off") }
+ };
+ const char *description_key = p->description_key ? p->description_key : p->name;
+@@ -5093,14 +5260,14 @@ void pa_alsa_profile_set_probe(
+ if (p->output_mappings)
+ PA_IDXSET_FOREACH(m, p->output_mappings, idx)
+ if (m->output_pcm) {
+- found_output |= !p->fallback_output;
++ found_output = true;
+ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths, mixers);
+ }
+
+ if (p->input_mappings)
+ PA_IDXSET_FOREACH(m, p->input_mappings, idx)
+ if (m->input_pcm) {
+- found_input |= !p->fallback_input;
++ found_input = true;
+ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths, mixers);
+ }
+ }
+diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
+index 905e3128b..db8310258 100644
+--- a/src/modules/alsa/alsa-mixer.h
++++ b/src/modules/alsa/alsa-mixer.h
+@@ -50,6 +50,8 @@ typedef struct pa_alsa_port_data pa_alsa_port_data;
+ #include "alsa-util.h"
+ #include "alsa-ucm.h"
+
++#define POSITION_MASK_CHANNELS 8
++
+ typedef enum pa_alsa_switch_use {
+ PA_ALSA_SWITCH_IGNORE,
+ PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */
+@@ -113,6 +115,8 @@ struct pa_alsa_mixer_id {
+ int index;
+ };
+
++char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id);
++
+ /* An option belongs to an element and refers to one enumeration item
+ * of the element is an enumeration item, or a switch status if the
+ * element is a switch item. */
+@@ -152,7 +156,7 @@ struct pa_alsa_element {
+
+ long constant_volume;
+
+- bool override_map:1;
++ unsigned int override_map;
+ bool direction_try_other:1;
+
+ bool has_dB:1;
+@@ -160,7 +164,7 @@ struct pa_alsa_element {
+ long volume_limit; /* -1 for no configured limit */
+ double min_dB, max_dB;
+
+- pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
++ pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
+ unsigned n_channels;
+
+ pa_channel_position_mask_t merged_mask;
+@@ -177,8 +181,8 @@ struct pa_alsa_jack {
+ snd_mixer_t *mixer_handle;
+ char *mixer_device_name;
+
++ struct pa_alsa_mixer_id alsa_id;
+ char *name; /* E g "Headphone" */
+- char *alsa_name; /* E g "Headphone Jack" */
+ bool has_control; /* is the jack itself present? */
+ bool plugged_in; /* is this jack currently plugged in? */
+ snd_mixer_elem_t *melem; /* Jack detection handle */
+@@ -194,7 +198,7 @@ struct pa_alsa_jack {
+ bool append_pcm_to_name;
+ };
+
+-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name);
++pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index);
+ void pa_alsa_jack_free(pa_alsa_jack *jack);
+ void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
+ void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
+diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
+index f7fef8a7e..bd8377ced 100644
+--- a/src/modules/alsa/alsa-sink.c
++++ b/src/modules/alsa/alsa-sink.c
+@@ -1877,8 +1877,11 @@ static int process_rewind(struct userdata *u) {
+ u->after_rewind = true;
+ return 0;
+ }
+- } else
++ } else {
+ pa_log_debug("Mhmm, actually there is nothing to rewind.");
++ if (u->use_tsched)
++ increase_watermark(u);
++ }
+
+ rewind_done:
+ pa_sink_process_rewind(u->sink, 0);
+diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
+index 18925b792..d9cea6105 100644
+--- a/src/modules/alsa/alsa-ucm.c
++++ b/src/modules/alsa/alsa-ucm.c
+@@ -1719,7 +1719,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
+ pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control);
+ return NULL;
+ }
+- j = pa_alsa_jack_new(NULL, mixer_device_name, name);
++ j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0);
+ PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
+
+ finish:
+@@ -1953,7 +1953,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) {
+ continue;
+ }
+
+- has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL;
++ has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL;
+ pa_alsa_jack_set_has_control(dev->jack, has_control);
+ pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control);
+ }
+diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
+index bf35b6112..172a7bb51 100644
+--- a/src/modules/alsa/alsa-util.c
++++ b/src/modules/alsa/alsa-util.c
+@@ -1635,8 +1635,8 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer,
+ return NULL;
+ }
+
+-snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) {
+- return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device);
++snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) {
++ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device);
+ }
+
+ snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) {
+diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
+index cb8be22fb..2eed3eac3 100644
+--- a/src/modules/alsa/alsa-util.h
++++ b/src/modules/alsa/alsa-util.h
+@@ -148,7 +148,7 @@ const char* pa_alsa_strerror(int errnum);
+
+ bool pa_alsa_may_tsched(bool want);
+
+-snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device);
++snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device);
+ snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device);
+
+ snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe);
+diff --git a/src/modules/alsa/mixer/paths/analog-output-chat.conf b/src/modules/alsa/mixer/paths/analog-output-chat.conf
+new file mode 100644
+index 000000000..360a1fceb
+--- /dev/null
++++ b/src/modules/alsa/mixer/paths/analog-output-chat.conf
+@@ -0,0 +1,5 @@
++; Some gaming devices have a separate "chat" device, this is for voice chat
++; while playing games. This device is just a fairly standard analog mono
++; device, but it's nicer to make it clear that this is the "chat" device
++; as is mentioned in the marketing info and manual.
++.include analog-output.conf.common
+diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
+index 30815d0a8..178999088 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
+@@ -13,17 +13,24 @@
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with PulseAudio; if not, see .
+
+-; Path for mixers that have a 'Headphone2' control
++; Path for the second headphone output on dual-headphone machines.
+ ;
+ ; See analog-output.conf.common for an explanation on the directives
+
+ [General]
+ priority = 98
+-description-key = analog-output-headphones
+
+ [Properties]
+ device.icon_name = audio-headphones
+
++; HP EliteDesk 800 SFF Headphone
++[Jack Front Headphone,1]
++required-any = any
++
++; HP EliteDesk 800 DM Headphone
++[Jack Front Headphone Surround]
++required-any = any
++
+ [Element Hardware Master]
+ switch = mute
+ volume = merge
+@@ -47,6 +54,13 @@ volume = off
+ switch = mute
+ volume = zero
+
++[Element Headphone,1]
++required-any = any
++switch = mute
++volume = merge
++override-map.1 = all
++override-map.2 = all-left,all-right
++
+ [Element Headphone+LO]
+ switch = mute
+ volume = zero
+@@ -56,7 +70,7 @@ switch = off
+ volume = off
+
+ [Element Headphone2]
+-required = any
++required-any = any
+ switch = mute
+ volume = merge
+ override-map.1 = all
+diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+index d4ad7777d..c808e19e1 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+@@ -35,6 +35,10 @@ state.unplugged = unknown
+ [Jack Front Headphone]
+ required-any = any
+
++; HP EliteDesk 800 DM Headset
++[Jack Front Headphone Front]
++required-any = any
++
+ [Jack Front Headphone Phantom]
+ required-any = any
+ state.plugged = unknown
+@@ -89,6 +93,13 @@ volume = merge
+ override-map.1 = all
+ override-map.2 = all-left,all-right
+
++; This path is intended to control the first headphones, not
++; the second headphones. But it should not hurt if we leave the second
++; headphone jack enabled nonetheless.
++[Element Headphone,1]
++switch = mute
++volume = zero
++
+ [Element Headset]
+ required-any = any
+ switch = mute
+@@ -160,4 +171,8 @@ volume = off
+ switch = off
+ volume = off
+
++[Element Speaker Center/LFE]
++switch = off
++volume = off
++
+ .include analog-output.conf.common
+diff --git a/src/modules/alsa/mixer/paths/analog-output-lineout.conf b/src/modules/alsa/mixer/paths/analog-output-lineout.conf
+index 9a6af3ad8..1ffce2225 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-lineout.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-lineout.conf
+@@ -127,6 +127,10 @@ required-any = any
+ switch = off
+ volume = off
+
++[Element Headphone,1]
++switch = off
++volume = off
++
+ [Element Headphone2]
+ switch = off
+ volume = off
+@@ -181,6 +185,12 @@ volume = merge
+ override-map.1 = all-center
+ override-map.2 = all-center,lfe
+
++[Element Center/LFE]
++switch = mute
++volume = merge
++override-map.1 = all-center
++override-map.2 = all-center,lfe
++
+ [Element Bass Speaker]
+ switch = off
+ volume = off
+diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf
+index 989654334..5e4940598 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-mono.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf
+@@ -44,6 +44,10 @@ override-map.2 = all-left,all-right
+ switch = mute
+ volume = zero
+
++[Element Headphone,1]
++switch = mute
++volume = zero
++
+ [Element Headphone+LO]
+ switch = mute
+ volume = zero
+diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
+index 71f356dce..756afa954 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf
+@@ -76,6 +76,10 @@ volume = off
+ switch = mute
+ volume = zero
+
++[Element Headphone,1]
++switch = mute
++volume = zero
++
+ [Element Headphone2]
+ switch = mute
+ volume = zero
+@@ -174,4 +178,10 @@ volume = merge
+ override-map.1 = all-center
+ override-map.2 = all-center,lfe
+
++[Element Center/LFE]
++switch = mute
++volume = merge
++override-map.1 = all-center
++override-map.2 = all-center,lfe
++
+ .include analog-output.conf.common
+diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
+index 27a3983d5..72f928fd9 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
+@@ -88,12 +88,23 @@ override-map.2 = all-left,all-right
+ switch = off
+ volume = off
+
++; Make sure the internal speakers are not auto-muted once the system has speakers
++[Element Auto-Mute Mode]
++enumeration = select
++
++[Option Auto-Mute Mode:Disabled]
++name = analog-output-speaker
++
+ ; This profile path is intended to control the speaker, let's mute headphones
+ ; else there will be a spike when plugging in headphones
+ [Element Headphone]
+ switch = off
+ volume = off
+
++[Element Headphone,1]
++switch = off
++volume = off
++
+ [Element Headphone2]
+ switch = off
+ volume = off
+@@ -220,6 +231,12 @@ volume = merge
+ override-map.1 = all-center
+ override-map.2 = all-center,lfe
+
++[Element Center/LFE]
++switch = mute
++volume = merge
++override-map.1 = all-center
++override-map.2 = all-center,lfe
++
+ [Element Speaker CLFE]
+ switch = mute
+ volume = merge
+diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf
+index e6ba98358..0f6b5f5a0 100644
+--- a/src/modules/alsa/mixer/paths/analog-output.conf
++++ b/src/modules/alsa/mixer/paths/analog-output.conf
+@@ -79,4 +79,10 @@ volume = merge
+ override-map.1 = all-center
+ override-map.2 = all-center,lfe
+
++[Element Center/LFE]
++switch = mute
++volume = merge
++override-map.1 = all-center
++override-map.2 = all-center,lfe
++
+ .include analog-output.conf.common
+diff --git a/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
+index e3f91cd6c..1a1e7944e 100644
+--- a/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
++++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf
+@@ -23,10 +23,12 @@
+ ; Steelseries Arctis 7
+ ; Steelseries Arctis Pro Wireless.
+ ; Lucidsound LS31
+-;
+-; This path doesn't provide hardware volume control, because the stereo
+-; output is controlled by the PCM element with index 1, and currently
+-; PulseAudio only supports elements with index 0.
+
+ [General]
+ description-key = analog-output-headphones
++
++[Element PCM,1]
++volume = merge
++switch = mute
++override-map.1 = all
++override-map.2 = all-left,all-right
+diff --git a/src/modules/alsa/mixer/paths/virtual-surround-7.1.conf b/src/modules/alsa/mixer/paths/virtual-surround-7.1.conf
+new file mode 100644
+index 000000000..7f111f276
+--- /dev/null
++++ b/src/modules/alsa/mixer/paths/virtual-surround-7.1.conf
+@@ -0,0 +1,5 @@
++[Element PCM,1]
++switch = mute
++volume = merge
++override-map.1 = all
++override-map.2 = all-left,all-right
+diff --git a/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf b/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf
+new file mode 100644
+index 000000000..cc74852c3
+--- /dev/null
++++ b/src/modules/alsa/mixer/profile-sets/behringer-umc22.conf
+@@ -0,0 +1,68 @@
++# 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.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, see .
++
++; Behringer U-Phoria UMC22
++;
++; Default mapping only allows to use stereo input and sound card has two
++; physical input channels.
++;
++; However in case of only using a single input channel (like condenser
++; microphone) only one channel will have any sound, which is often
++; inconvenient for casual use.
++;
++; This config includes mono input options which makes it much more
++; friendly in single input configuration.
++;
++; This config also removes default digital input/output mappings that do
++; not physically exist on this card.
++;
++; Added by Nazar Mokrynskyi
++
++[General]
++auto-profiles = yes
++
++[Mapping analog-stereo-input]
++device-strings = hw:%f
++channel-map = left,right
++paths-input = analog-input-mic
++direction = input
++priority = 4
++
++[Mapping analog-mono]
++device-strings = hw:%f
++channel-map = mono,mono
++paths-input = analog-input-mic
++direction = input
++priority = 3
++
++[Mapping analog-mono-left]
++device-strings = hw:%f
++channel-map = mono,aux1
++paths-input = analog-input-mic
++direction = input
++priority = 2
++
++[Mapping analog-mono-right]
++device-strings = hw:%f
++channel-map = aux1,mono
++paths-input = analog-input-mic
++direction = input
++priority = 1
++
++[Mapping analog-stereo-output]
++device-strings = front:%f
++channel-map = left,right
++paths-output = analog-output
++direction = output
+diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf
+new file mode 100644
+index 000000000..a683a4e4e
+--- /dev/null
++++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-120w-g2.conf
+@@ -0,0 +1,35 @@
++# 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.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, see .
++
++; HP Thunderbolt Dock 120W G2
++;
++; This dock has a 3.5mm headset connector. Both input and output are stereo.
++;
++; There's a separate speakerphone module called "HP Thunderbolt Dock Audio
++; Module", which can be attached to this dock. The module will appear in ALSA
++; as a separate USB sound card, configuration for it is in
++; hp-tbt-dock-audio-module.conf.
++
++[General]
++auto-profiles = no
++
++[Mapping analog-stereo-headset]
++device-strings = hw:%f,0,0
++channel-map = left,right
++
++[Profile output:analog-stereo-headset+input:analog-stereo-headset]
++output-mappings = analog-stereo-headset
++input-mappings = analog-stereo-headset
++skip-probe = yes
+diff --git a/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf
+new file mode 100644
+index 000000000..692ab8dd0
+--- /dev/null
++++ b/src/modules/alsa/mixer/profile-sets/hp-tbt-dock-audio-module.conf
+@@ -0,0 +1,36 @@
++# 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.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, see .
++
++; HP Thunderbolt Dock Audio Module
++;
++; This device attaches to the "HP Thunderbolt Dock 120W G2" dock. The audio
++; module provides a speakerphone with echo cancellation and appears in ALSA as
++; a USB sound card with stereo input and output.
++;
++; The dock itself has a 3.5mm headset connector and appears as a separate USB
++; sound card, configuration for it is in hp-tbt-dock-120w-g2.conf.
++
++[General]
++auto-profiles = no
++
++[Mapping analog-stereo-speakerphone]
++device-strings = hw:%f,0,0
++channel-map = left,right
++intended-roles = phone
++
++[Profile output:analog-stereo-speakerphone+input:analog-stereo-speakerphone]
++output-mappings = analog-stereo-speakerphone
++input-mappings = analog-stereo-speakerphone
++skip-probe = yes
+diff --git a/src/modules/alsa/mixer/profile-sets/sennheiser-gsx.conf b/src/modules/alsa/mixer/profile-sets/sennheiser-gsx.conf
+new file mode 100644
+index 000000000..0ac157685
+--- /dev/null
++++ b/src/modules/alsa/mixer/profile-sets/sennheiser-gsx.conf
+@@ -0,0 +1,58 @@
++# 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.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, see .
++
++; USB Gaming DAC.
++; These devices have two output devices. The first one is mono, meant for
++; voice audio, and the second one is 7.1 surround, meant for everything
++; else. The 7.1 surround is mapped to headphones within the device.
++; The purpose of the mono/7.1 design is to provide separate volume
++; controls for voice and other audio, which can be useful in gaming.
++;
++; Works with:
++; Sennheiser GSX 1000
++; Sennheiser GSX 1200
++;
++; See default.conf for an explanation on the directives used here.
++
++[General]
++auto-profiles = no
++
++[Mapping analog-chat-output]
++device-strings = hw:%f,0
++channel-map = mono
++paths-output = analog-chat-output
++direction = output
++priority = 4000
++intended-roles = phone
++
++[Mapping analog-output-surround71]
++device-strings = hw:%f,1
++channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
++paths-output = virtual-surround-7.1
++priority = 4100
++direction = output
++
++[Mapping analog-chat-input]
++device-strings = hw:%f,0
++channel-map = mono
++paths-input = analog-chat-input
++priority = 4100
++direction = input
++
++[Profile output:analog-output-surround71+output:analog-output-chat+input:analog-input]
++output-mappings = analog-output-surround71 analog-chat-output
++input-mappings = analog-chat-input
++priority = 5100
++skip-probe = yes
+diff --git a/src/modules/alsa/mixer/profile-sets/simple-headphones-mic.conf b/src/modules/alsa/mixer/profile-sets/simple-headphones-mic.conf
+new file mode 100644
+index 000000000..809d01587
+--- /dev/null
++++ b/src/modules/alsa/mixer/profile-sets/simple-headphones-mic.conf
+@@ -0,0 +1,42 @@
++# 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.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, see .
++
++; This is a profile meant for simple (stereo + mic) headphones.
++; default.conf also works but using this one will hide some profiles
++; that don't make sense like IEC958 and multichannel inputs.
++
++[General]
++auto-profiles = yes
++
++[Mapping analog-stereo]
++device-strings = front:%f
++channel-map = left,right
++paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
++paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
++priority = 15
++
++# If everything else fails, try to use hw:0 as a stereo device...
++[Mapping stereo-fallback]
++device-strings = hw:%f
++fallback = yes
++channel-map = front-left,front-right
++paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
++paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
++priority = 1
++
++[Mapping analog-mono]
++device-strings = hw:%f,0,0
++channel-map = mono
++direction = input
+diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
+index de2fe9cc4..7ff82395f 100644
+--- a/src/modules/alsa/module-alsa-card.c
++++ b/src/modules/alsa/module-alsa-card.c
+@@ -104,6 +104,8 @@ static const char* const valid_modargs[] = {
+
+ #define DEFAULT_DEVICE_ID "0"
+
++#define PULSE_MODARGS "PULSE_MODARGS"
++
+ struct userdata {
+ pa_core *core;
+ pa_module *module;
+@@ -621,6 +623,7 @@ static void init_jacks(struct userdata *u) {
+ void *state;
+ pa_alsa_path* path;
+ pa_alsa_jack* jack;
++ char buf[64];
+
+ u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+@@ -663,9 +666,10 @@ static void init_jacks(struct userdata *u) {
+ }
+ }
+ pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop);
+- jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0);
++ jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0);
+ if (!jack->melem) {
+- pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
++ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id);
++ pa_log_warn("Jack %s seems to have disappeared.", buf);
+ pa_alsa_jack_set_has_control(jack, false);
+ continue;
+ }
+@@ -820,6 +824,7 @@ int pa__init(pa_module *m) {
+ const char *description;
+ const char *profile_str = NULL;
+ char *fn = NULL;
++ char *udev_args = NULL;
+ bool namereg_fail = false;
+ int err = -PA_MODULE_ERR_UNSPECIFIED, rval;
+
+@@ -849,6 +854,47 @@ int pa__init(pa_module *m) {
+ goto fail;
+ }
+
++#ifdef HAVE_UDEV
++ udev_args = pa_udev_get_property(u->alsa_card_index, PULSE_MODARGS);
++#endif
++
++ if (udev_args) {
++ bool udev_modargs_success = true;
++ pa_modargs *temp_ma = pa_modargs_new(udev_args, valid_modargs);
++
++ if (temp_ma) {
++ /* do not try to replace device_id */
++
++ if (pa_modargs_remove_key(temp_ma, "device_id") == 0) {
++ pa_log_warn("Unexpected 'device_id' module argument override ignored from udev " PULSE_MODARGS "='%s'", udev_args);
++ }
++
++ /* Implement modargs override by copying original module arguments
++ * over udev entry arguments ignoring duplicates. */
++
++ if (pa_modargs_merge_missing(temp_ma, u->modargs, valid_modargs) == 0) {
++ /* swap module arguments */
++ pa_modargs *old_ma = u->modargs;
++ u->modargs = temp_ma;
++ temp_ma = old_ma;
++
++ pa_log_info("Applied module arguments override from udev " PULSE_MODARGS "='%s'", udev_args);
++ } else {
++ pa_log("Failed to apply module arguments override from udev " PULSE_MODARGS "='%s'", udev_args);
++ udev_modargs_success = false;
++ }
++
++ pa_modargs_free(temp_ma);
++ } else {
++ pa_log("Failed to parse module arguments from udev " PULSE_MODARGS "='%s'", udev_args);
++ udev_modargs_success = false;
++ }
++ pa_xfree(udev_args);
++
++ if (!udev_modargs_success)
++ goto fail;
++ }
++
+ if (pa_modargs_get_value_boolean(u->modargs, "ignore_dB", &ignore_dB) < 0) {
+ pa_log("Failed to parse ignore_dB argument.");
+ goto fail;
+diff --git a/src/modules/bluetooth/backend-ofono.c b/src/modules/bluetooth/backend-ofono.c
+index 0e5bbe8b7..d7a13efd0 100644
+--- a/src/modules/bluetooth/backend-ofono.c
++++ b/src/modules/bluetooth/backend-ofono.c
+@@ -627,8 +627,6 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
+
+ card = pa_hashmap_get(backend->cards, path);
+
+- card->connecting = false;
+-
+ if (!card || codec != HFP_AUDIO_CODEC_CVSD || card->fd >= 0) {
+ pa_log_warn("New audio connection invalid arguments (path=%s fd=%d, codec=%d)", path, fd, codec);
+ pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call"));
+@@ -639,6 +637,7 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
+
+ pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", path, fd, codec);
+
++ card->connecting = false;
+ card->fd = fd;
+ card->transport->codec = codec;
+
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index 402053a59..9440084a6 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -279,10 +279,6 @@ static int sco_process_render(struct userdata *u) {
+
+ saved_errno = errno;
+
+- if (saved_errno == EINTR)
+- /* Retry right away if we got interrupted */
+- continue;
+-
+ pa_memblock_unref(memchunk.memblock);
+
+ if (saved_errno == EAGAIN) {
+@@ -462,11 +458,7 @@ static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
+
+ if (l < 0) {
+
+- if (errno == EINTR)
+- /* Retry right away if we got interrupted */
+- continue;
+-
+- else if (errno == EAGAIN) {
++ if (errno == EAGAIN) {
+ /* Hmm, apparently the socket was not writable, give up for now */
+ pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
+ break;
+@@ -1492,7 +1484,7 @@ static void thread_func(void *userdata) {
+ if (bytes_to_send > 2 * u->write_block_size) {
+ uint64_t skip_bytes;
+ pa_memchunk tmp;
+- size_t mempool_max_block_size = pa_mempool_block_size_max(u->core->mempool);
++ size_t max_render_size = pa_frame_align(pa_mempool_block_size_max(u->core->mempool), &u->encoder_sample_spec);
+ pa_usec_t skip_usec;
+
+ skip_bytes = bytes_to_send - 2 * u->write_block_size;
+@@ -1505,8 +1497,8 @@ static void thread_func(void *userdata) {
+ while (skip_bytes > 0) {
+ size_t bytes_to_render;
+
+- if (skip_bytes > mempool_max_block_size)
+- bytes_to_render = mempool_max_block_size;
++ if (skip_bytes > max_render_size)
++ bytes_to_render = max_render_size;
+ else
+ bytes_to_render = skip_bytes;
+
+diff --git a/src/modules/jack/module-jack-sink.c b/src/modules/jack/module-jack-sink.c
+index effa0dd01..a7c723073 100644
+--- a/src/modules/jack/module-jack-sink.c
++++ b/src/modules/jack/module-jack-sink.c
+@@ -400,7 +400,7 @@ int pa__init(pa_module*m) {
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
+ if (server_name)
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name);
+- pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client));
++ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "JACK sink (%s)", jack_get_client_name(u->client));
+ pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client));
+
+ if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+diff --git a/src/modules/jack/module-jack-source.c b/src/modules/jack/module-jack-source.c
+index eaf2cd81c..c4541913c 100644
+--- a/src/modules/jack/module-jack-source.c
++++ b/src/modules/jack/module-jack-source.c
+@@ -342,7 +342,7 @@ int pa__init(pa_module*m) {
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
+ if (server_name)
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name);
+- pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client));
++ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "JACK source (%s)", jack_get_client_name(u->client));
+ pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client));
+
+ if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+diff --git a/src/modules/jack/module-jackdbus-detect.c b/src/modules/jack/module-jackdbus-detect.c
+index 63c430748..0a9f24c96 100644
+--- a/src/modules/jack/module-jackdbus-detect.c
++++ b/src/modules/jack/module-jackdbus-detect.c
+@@ -25,12 +25,14 @@
+ #include
+ #endif
+
++#include
+ #include
+
+ #include
+ #include
+ #include
+ #include
++#include
+
+ PA_MODULE_AUTHOR("David Henningsson");
+ PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started");
+@@ -38,8 +40,16 @@ PA_MODULE_LOAD_ONCE(true);
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+ PA_MODULE_USAGE(
+ "channels= "
+- "source_channels= "
++ "sink_name= "
++ "sink_properties= "
++ "sink_client_name= "
+ "sink_channels= "
++ "sink_channel_map= "
++ "source_name= "
++ "source_properties= "
++ "source_client_name= "
++ "source_channels= "
++ "source_channel_map= "
+ "connect=");
+
+ #define JACK_SERVICE_NAME "org.jackaudio.service"
+@@ -61,8 +71,16 @@ PA_MODULE_USAGE(
+
+ static const char* const valid_modargs[] = {
+ "channels",
+- "source_channels",
++ "sink_name",
++ "sink_properties",
++ "sink_client_name",
+ "sink_channels",
++ "sink_channel_map",
++ "source_name",
++ "source_properties",
++ "source_client_name",
++ "source_channels",
++ "source_channel_map",
+ "connect",
+ NULL
+ };
+@@ -76,6 +94,19 @@ static const char* const modnames[JACK_SS_COUNT] = {
+ "module-jack-source"
+ };
+
++static const char* const modtypes[JACK_SS_COUNT] = {
++ "sink",
++ "source"
++};
++
++struct moddata {
++ char *name;
++ pa_proplist *proplist;
++ char *client_name;
++ uint32_t channels;
++ pa_channel_map channel_map;
++};
++
+ struct userdata {
+ pa_module *module;
+ pa_core *core;
+@@ -83,13 +114,13 @@ struct userdata {
+ bool filter_added, match_added;
+ bool is_service_started;
+ bool autoconnect_ports;
+- uint32_t channels[JACK_SS_COUNT];
++ struct moddata mod_args[JACK_SS_COUNT];
+ /* Using index here protects us from module unloading without us knowing */
+ int jack_module_index[JACK_SS_COUNT];
+ };
+
+ static void ensure_ports_stopped(struct userdata* u) {
+- int i;
++ unsigned i;
+ pa_assert(u);
+
+ for (i = 0; i < JACK_SS_COUNT; i++)
+@@ -100,19 +131,81 @@ static void ensure_ports_stopped(struct userdata* u) {
+ }
+ }
+
++static char* proplist_to_arg(pa_proplist *p) {
++ const char *key;
++ void *state = NULL;
++ pa_strbuf *buf;
++
++ pa_assert(p);
++
++ buf = pa_strbuf_new();
++
++ while ((key = pa_proplist_iterate(p, &state))) {
++ const char *v;
++ char *escaped;
++
++ if (!pa_strbuf_isempty(buf))
++ pa_strbuf_puts(buf, " ");
++
++ if ((v = pa_proplist_gets(p, key))) {
++ pa_strbuf_printf(buf, "%s=\"", key);
++
++ escaped = pa_escape(v, "\"'");
++ pa_strbuf_puts(buf, escaped);
++ pa_xfree(escaped);
++
++ pa_strbuf_puts(buf, "\"");
++ } else {
++ const void *value;
++ size_t nbytes;
++ char *c;
++
++ pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0);
++ c = pa_xmalloc(nbytes*2+1);
++ pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1);
++
++ pa_strbuf_printf(buf, "%s=hex:%s", key, c);
++ pa_xfree(c);
++ }
++ }
++
++ return pa_strbuf_to_string_free(buf);
++}
++
+ static void ensure_ports_started(struct userdata* u) {
+- int i;
++ unsigned i;
++ char *escaped;
+ pa_assert(u);
+
+ for (i = 0; i < JACK_SS_COUNT; i++)
+ if (!u->jack_module_index[i]) {
+- char* args;
+- pa_module* m;
+- if (u->channels[i] > 0) {
+- args = pa_sprintf_malloc("connect=%s channels=%" PRIu32, pa_yes_no(u->autoconnect_ports), u->channels[i]);
+- } else {
+- args = pa_sprintf_malloc("connect=%s", pa_yes_no(u->autoconnect_ports));
++ pa_strbuf *args_buf = pa_strbuf_new();
++ char *args;
++ pa_module *m;
++ pa_strbuf_printf(args_buf, "connect=%s", pa_yes_no(u->autoconnect_ports));
++ if (u->mod_args[i].name) {
++ escaped = pa_escape(u->mod_args[i].name, "'");
++ pa_strbuf_printf(args_buf, " %s_name='%s'", modtypes[i], escaped);
++ pa_xfree(escaped);
++ }
++ if (!pa_proplist_isempty(u->mod_args[i].proplist)) {
++ escaped = proplist_to_arg(u->mod_args[i].proplist);
++ pa_strbuf_printf(args_buf, " %s_properties='%s'", modtypes[i], escaped);
++ pa_xfree(escaped);
++ }
++ if (u->mod_args[i].client_name) {
++ escaped = pa_escape(u->mod_args[i].client_name, "'");
++ pa_strbuf_printf(args_buf, " client_name='%s'", escaped);
++ pa_xfree(escaped);
++ }
++ if (u->mod_args[i].channels > 0)
++ pa_strbuf_printf(args_buf, " channels=%" PRIu32, u->mod_args[i].channels);
++ if (u->mod_args[i].channel_map.channels > 0) {
++ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
++ pa_channel_map_snprint(cm, sizeof(cm), &u->mod_args[i].channel_map);
++ pa_strbuf_printf(args_buf, " channel_map='%s'", cm);
+ }
++ args = pa_strbuf_to_string_free(args_buf);
+ pa_module_load(&m, u->core, modnames[i], args);
+ pa_xfree(args);
+
+@@ -218,7 +311,9 @@ int pa__init(pa_module *m) {
+ struct userdata *u = NULL;
+ pa_modargs *ma;
+ uint32_t channels = 0;
+- int i;
++ unsigned i;
++ char argname[32];
++ const char *name;
+
+ pa_assert(m);
+
+@@ -243,18 +338,40 @@ int pa__init(pa_module *m) {
+ pa_log("Failed to parse channels= argument.");
+ goto fail;
+ }
++
+ for (i = 0; i < JACK_SS_COUNT; i++) {
+- u->channels[i] = channels;
+- }
++ pa_snprintf(argname, sizeof(argname), "%s_name", modtypes[i]);
++ name = pa_modargs_get_value(ma, argname, NULL);
++ u->mod_args[i].name = pa_xstrdup(name);
++
++ u->mod_args[i].proplist = pa_proplist_new();
++ pa_snprintf(argname, sizeof(argname), "%s_properties", modtypes[i]);
++ if (pa_modargs_get_proplist(ma, argname, u->mod_args[i].proplist, PA_UPDATE_REPLACE) < 0) {
++ pa_log("Invalid %s properties", modtypes[i]);
++ goto fail;
++ }
+
+- if (pa_modargs_get_value_u32(ma, "source_channels", &u->channels[JACK_SS_SOURCE]) < 0 || (u->channels[JACK_SS_SOURCE] > 0 && !pa_channels_valid(u->channels[JACK_SS_SOURCE]))) {
+- pa_log("Failed to parse source_channels= argument.");
+- goto fail;
+- }
++ pa_snprintf(argname, sizeof(argname), "%s_client_name", modtypes[i]);
++ name = pa_modargs_get_value(ma, argname, NULL);
++ u->mod_args[i].client_name = pa_xstrdup(name);
+
+- if (pa_modargs_get_value_u32(ma, "sink_channels", &u->channels[JACK_SS_SINK]) < 0 || (u->channels[JACK_SS_SINK] > 0 && !pa_channels_valid(u->channels[JACK_SS_SINK]))) {
+- pa_log("Failed to parse sink_channels= argument.");
+- goto fail;
++ u->mod_args[i].channels = channels;
++ pa_snprintf(argname, sizeof(argname), "%s_channels", modtypes[i]);
++ if (pa_modargs_get_value_u32(ma, argname, &u->mod_args[i].channels) < 0
++ || (u->mod_args[i].channels > 0 && !pa_channels_valid(u->mod_args[i].channels))) {
++ pa_log("Failed to parse %s= argument.", argname);
++ goto fail;
++ }
++
++ pa_channel_map_init(&u->mod_args[i].channel_map);
++ pa_snprintf(argname, sizeof(argname), "%s_channel_map", modtypes[i]);
++ if (pa_modargs_get_value(ma, argname, NULL)) {
++ if (pa_modargs_get_channel_map(ma, argname, &u->mod_args[i].channel_map) < 0
++ || (u->mod_args[i].channels > 0 && u->mod_args[i].channel_map.channels != u->mod_args[i].channels)) {
++ pa_log("Failed to parse %s= argument.", argname);
++ goto fail;
++ }
++ }
+ }
+
+ if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
+@@ -298,6 +415,7 @@ fail:
+
+ void pa__done(pa_module *m) {
+ struct userdata *u;
++ unsigned i;
+
+ pa_assert(m);
+
+@@ -320,5 +438,14 @@ void pa__done(pa_module *m) {
+ pa_dbus_connection_unref(u->connection);
+ }
+
++ for (i = 0; i < JACK_SS_COUNT; i++) {
++ pa_xfree(u->mod_args[i].name);
++
++ if (u->mod_args[i].proplist)
++ pa_proplist_free(u->mod_args[i].proplist);
++
++ pa_xfree(u->mod_args[i].client_name);
++ }
++
+ pa_xfree(u);
+ }
+diff --git a/src/modules/meson.build b/src/modules/meson.build
+index 5f0437164..dcfc432d8 100644
+--- a/src/modules/meson.build
++++ b/src/modules/meson.build
+@@ -1,4 +1,6 @@
+-subdir('rtp')
++if host_machine.system() != 'windows'
++ subdir('rtp')
++endif
+
+ # module name, sources, [headers, extra flags, extra deps, extra libs]
+ all_modules = [
+@@ -44,8 +46,6 @@ all_modules = [
+ [ 'module-rescue-streams', 'module-rescue-streams.c' ],
+ [ 'module-role-cork', ['module-role-cork.c', 'stream-interaction.c'], 'stream-interaction.h' ],
+ [ 'module-role-ducking', ['module-role-ducking.c', 'stream-interaction.c'], 'stream-interaction.h' ],
+- [ 'module-rtp-recv', 'rtp/module-rtp-recv.c', [], [], [], librtp ],
+- [ 'module-rtp-send', 'rtp/module-rtp-send.c' , [], [], [], librtp ],
+ [ 'module-simple-protocol-tcp', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_SIMPLE', '-DUSE_TCP_SOCKETS'], [], libprotocol_simple ],
+ [ 'module-simple-protocol-unix', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_SIMPLE', '-DUSE_UNIX_SOCKETS'], [], libprotocol_simple ],
+ [ 'module-sine', 'module-sine.c' ],
+@@ -61,11 +61,24 @@ all_modules = [
+ [ 'module-tunnel-source-new', 'module-tunnel-source-new.c' ],
+ [ 'module-virtual-sink', 'module-virtual-sink.c' ],
+ [ 'module-virtual-source', 'module-virtual-source.c' ],
+- [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c' ],
+ [ 'module-volume-restore', 'module-volume-restore.c' ],
+-# [ 'module-waveout', 'module-waveout.c' ],
+ ]
+
++if host_machine.system() == 'windows'
++ winmm_dep = meson.get_compiler('c').find_library('winmm')
++ ksuser_dep = meson.get_compiler('c').find_library('ksuser')
++ all_modules += [
++ [ 'module-waveout', 'module-waveout.c', [], [], [winmm_dep, ksuser_dep] ],
++ ]
++endif
++
++if host_machine.system() != 'windows'
++ all_modules += [
++ [ 'module-rtp-recv', 'rtp/module-rtp-recv.c', [], [], [], librtp ],
++ [ 'module-rtp-send', 'rtp/module-rtp-send.c' , [], [], [], librtp ],
++ ]
++endif
++
+ # Modules enabled by headers
+
+ if cc.has_header('linux/input.h')
+@@ -74,7 +87,7 @@ if cc.has_header('linux/input.h')
+ ]
+ endif
+
+-if cc.has_header('sys/soundcard.h')
++if cdata.has('HAVE_OSS_OUTPUT')
+ subdir('oss')
+ all_modules += [
+ [ 'module-oss', 'oss/module-oss.c', [], [], [], liboss_util ],
+@@ -137,6 +150,12 @@ if dbus_dep.found()
+ ]
+ endif
+
++if fftw_dep.found()
++ all_modules += [
++ [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c', [], [], [fftw_dep, libm_dep] ],
++ ]
++endif
++
+ if dbus_dep.found() and fftw_dep.found()
+ all_modules += [
+ [ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep] ],
+@@ -171,10 +190,13 @@ if lirc_dep.found()
+ endif
+
+ if openssl_dep.found()
+- subdir('raop')
+- all_modules += [
+- [ 'module-raop-sink', 'raop/module-raop-sink.c', [], [], [], libraop ],
+- ]
++ if host_machine.system() != 'windows'
++ subdir('raop')
++ all_modules += [
++ [ 'module-raop-sink', 'raop/module-raop-sink.c', [], [], [], libraop ],
++ ]
++ endif
++
+ if avahi_dep.found()
+ all_modules += [
+ [ 'module-raop-discover', 'raop/module-raop-discover.c', [], [], [avahi_dep], libavahi_wrap ],
+@@ -284,7 +306,7 @@ foreach m : all_modules
+ install : true,
+ install_rpath : rpath_dirs,
+ install_dir : modlibexecdir,
+- dependencies : [thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep] + extra_deps,
++ dependencies : [thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep, platform_dep, platform_socket_dep] + extra_deps,
+ link_args : [nodelete_link_args, '-Wl,--no-undefined' ],
+ link_with : extra_libs,
+ name_prefix : '',
+diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
+index d15d9ffa3..684c8360d 100644
+--- a/src/modules/module-device-restore.c
++++ b/src/modules/module-device-restore.c
+@@ -528,6 +528,8 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry *
+ char port[PA_NAME_MAX];
+ } PA_GCC_PACKED;
+ struct legacy_entry *le;
++ pa_channel_map channel_map;
++ pa_cvolume volume;
+
+ pa_assert(u);
+ pa_assert(data);
+@@ -551,12 +553,17 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry *
+ return false;
+ }
+
+- if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) {
++ /* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these
++ * members to be accessed directly */
++ channel_map = le->channel_map;
++ volume = le->volume;
++
++ if (le->volume_valid && !pa_channel_map_valid(&channel_map)) {
+ pa_log_warn("Invalid channel map.");
+ return false;
+ }
+
+- if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) {
++ if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) {
+ pa_log_warn("Volume and channel map don't match.");
+ return false;
+ }
+diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c
+index 5ff04516a..f46dc3889 100644
+--- a/src/modules/module-esound-sink.c
++++ b/src/modules/module-esound-sink.c
+@@ -249,9 +249,7 @@ static void thread_func(void *userdata) {
+
+ if (l < 0) {
+
+- if (errno == EINTR)
+- continue;
+- else if (errno == EAGAIN) {
++ if (errno == EAGAIN) {
+
+ /* OK, we filled all socket buffers up
+ * now. */
+diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c
+index c9f3f3932..1c1278218 100644
+--- a/src/modules/module-filter-apply.c
++++ b/src/modules/module-filter-apply.c
+@@ -146,15 +146,20 @@ static const char* get_filter_name(pa_object *o, bool is_sink_input) {
+ static const char* get_filter_parameters(pa_object *o, const char *want, bool is_sink_input) {
+ const char *parameters;
+ char *prop_parameters;
+- pa_proplist *pl;
++ pa_proplist *pl, *device_pl;
+
+- if (is_sink_input)
++ if (is_sink_input) {
+ pl = PA_SINK_INPUT(o)->proplist;
+- else
++ device_pl = PA_SINK_INPUT(o)->sink->proplist;
++ } else {
+ pl = PA_SOURCE_OUTPUT(o)->proplist;
++ device_pl = PA_SOURCE_OUTPUT(o)->source->proplist;
++ }
+
+ prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, want);
+ parameters = pa_proplist_gets(pl, prop_parameters);
++ if (!parameters)
++ parameters = pa_proplist_gets(device_pl, prop_parameters);
+ pa_xfree(prop_parameters);
+
+ return parameters;
+diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
+index f4fed6d64..5726d0818 100644
+--- a/src/modules/module-ladspa-sink.c
++++ b/src/modules/module-ladspa-sink.c
+@@ -714,6 +714,9 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, p
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
++ if (!PA_SINK_IS_LINKED(u->sink->state))
++ return;
++
+ if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE)
+ pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE);
+ else
+diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
+index 871f01166..4f8ceec46 100644
+--- a/src/modules/module-loopback.c
++++ b/src/modules/module-loopback.c
+@@ -1016,6 +1016,14 @@ static void set_sink_input_latency(struct userdata *u, pa_sink *sink) {
+ if (u->min_source_latency > requested_latency) {
+ latency = PA_MAX(u->latency, u->minimum_latency);
+ requested_latency = (latency - u->min_source_latency) / 2;
++ /* In the case of a fixed alsa source, u->minimum_latency is calculated from
++ * the default fragment size while u->min_source_latency is the reported minimum
++ * of the source latency (nr_of_fragments * fragment_size). This can lead to a
++ * situation where u->minimum_latency < u->min_source_latency. We only fall
++ * back to use the fragment size instead of min_source_latency if the calculation
++ * above does not deliver a usable result. */
++ if (u->fixed_alsa_source && u->min_source_latency >= latency)
++ requested_latency = (latency - u->core->default_fragment_size_msec * PA_USEC_PER_MSEC) / 2;
+ }
+
+ latency = PA_CLAMP(requested_latency , u->min_sink_latency, u->max_sink_latency);
+diff --git a/src/modules/module-match.c b/src/modules/module-match.c
+index 76f71256c..9b5e76cd7 100644
+--- a/src/modules/module-match.c
++++ b/src/modules/module-match.c
+@@ -47,7 +47,7 @@
+ PA_MODULE_AUTHOR("Lennart Poettering");
+ PA_MODULE_DESCRIPTION("Playback stream expression matching module");
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+-PA_MODULE_LOAD_ONCE(true);
++PA_MODULE_LOAD_ONCE(false);
+ PA_MODULE_USAGE("table= "
+ "key=");
+
+diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
+index bbbf83435..6865663da 100644
+--- a/src/modules/module-null-sink.c
++++ b/src/modules/module-null-sink.c
+@@ -57,8 +57,8 @@ PA_MODULE_USAGE(
+ "norewinds=");
+
+ #define DEFAULT_SINK_NAME "null"
+-#define BLOCK_USEC (PA_USEC_PER_SEC * 2)
+-#define NOREWINDS_MAX_LATENCY_USEC (50*PA_USEC_PER_MSEC)
++#define BLOCK_USEC (2 * PA_USEC_PER_SEC)
++#define BLOCK_USEC_NOREWINDS (50 * PA_USEC_PER_MSEC)
+
+ struct userdata {
+ pa_core *core;
+@@ -318,6 +318,7 @@ int pa__init(pa_module*m) {
+ u->core = m->core;
+ u->module = m;
+ u->rtpoll = pa_rtpoll_new();
++ u->block_usec = BLOCK_USEC;
+
+ if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
+ pa_log("pa_thread_mq_init() failed.");
+@@ -381,13 +382,15 @@ int pa__init(pa_module*m) {
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+
+- u->block_usec = BLOCK_USEC;
+- nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
+-
+ if(pa_modargs_get_value_boolean(ma, "norewinds", &u->norewinds) < 0){
+ pa_log("Invalid argument, norewinds expects a boolean value.");
+ }
+
++ if (u->norewinds)
++ u->block_usec = BLOCK_USEC_NOREWINDS;
++
++ nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
++
+ if(u->norewinds){
+ pa_sink_set_max_rewind(u->sink, 0);
+ } else {
+@@ -401,11 +404,7 @@ int pa__init(pa_module*m) {
+ goto fail;
+ }
+
+- if(u->norewinds){
+- pa_sink_set_latency_range(u->sink, 0, NOREWINDS_MAX_LATENCY_USEC);
+- } else {
+- pa_sink_set_latency_range(u->sink, 0, BLOCK_USEC);
+- }
++ pa_sink_set_latency_range(u->sink, 0, u->block_usec);
+
+ pa_sink_put(u->sink);
+
+diff --git a/src/modules/module-null-source.c b/src/modules/module-null-source.c
+index 8d3796e9a..3aefbf2c5 100644
+--- a/src/modules/module-null-source.c
++++ b/src/modules/module-null-source.c
+@@ -52,6 +52,7 @@ PA_MODULE_USAGE(
+ "rate= "
+ "source_name= "
+ "channel_map= "
++ "max_latency_msec= "
+ "description= ");
+
+ #define DEFAULT_SOURCE_NAME "source.null"
+@@ -79,6 +80,7 @@ static const char* const valid_modargs[] = {
+ "channels",
+ "source_name",
+ "channel_map",
++ "max_latency_msec",
+ "description",
+ NULL
+ };
+@@ -123,6 +125,7 @@ static void source_update_requested_latency_cb(pa_source *s) {
+ pa_assert(u);
+
+ u->block_usec = pa_source_get_requested_latency_within_thread(s);
++
+ if (u->block_usec == (pa_usec_t)-1)
+ u->block_usec = u->source->thread_info.max_latency;
+ }
+@@ -197,6 +200,7 @@ int pa__init(pa_module*m) {
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ pa_source_new_data data;
++ uint32_t max_latency_msec;
+
+ pa_assert(m);
+
+@@ -247,7 +251,14 @@ int pa__init(pa_module*m) {
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+
+- pa_source_set_latency_range(u->source, MIN_LATENCY_USEC, MAX_LATENCY_USEC);
++ max_latency_msec = MAX_LATENCY_USEC / PA_USEC_PER_MSEC;
++ if (pa_modargs_get_value_u32(ma, "max_latency_msec", &max_latency_msec) < 0) {
++ pa_log("Failed to get max_latency_msec.");
++ goto fail;
++ }
++
++ pa_source_set_latency_range(u->source, MIN_LATENCY_USEC, max_latency_msec * PA_USEC_PER_MSEC);
++
+ u->block_usec = u->source->thread_info.max_latency;
+
+ u->source->thread_info.max_rewind =
+diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
+index 213924fdc..43595420f 100644
+--- a/src/modules/module-pipe-sink.c
++++ b/src/modules/module-pipe-sink.c
+@@ -199,14 +199,13 @@ static ssize_t pipe_sink_write(struct userdata *u, pa_memchunk *pchunk) {
+ if (l < 0) {
+ if (errno == EAGAIN)
+ break;
+- else if (errno != EINTR) {
+- if (!u->fifo_error) {
+- pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+- u->fifo_error = true;
+- }
+- count = -1 - count;
+- break;
++
++ if (!u->fifo_error) {
++ pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
++ u->fifo_error = true;
+ }
++ count = -1 - count;
++ break;
+ } else {
+ if (u->fifo_error) {
+ pa_log_debug("Recovered from FIFO error");
+@@ -288,9 +287,7 @@ static int process_render(struct userdata *u) {
+
+ if (l < 0) {
+
+- if (errno == EINTR)
+- continue;
+- else if (errno == EAGAIN)
++ if (errno == EAGAIN)
+ return 0;
+ else {
+ pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
+index 74ec0551a..32b35c163 100644
+--- a/src/modules/module-pipe-source.c
++++ b/src/modules/module-pipe-source.c
+@@ -155,9 +155,7 @@ static void thread_func(void *userdata) {
+
+ if (l < 0) {
+
+- if (errno == EINTR)
+- continue;
+- else if (errno != EAGAIN) {
++ if (errno != EAGAIN) {
+ pa_log("Failed to read data from FIFO: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c
+index a228208dc..5dd3fe079 100644
+--- a/src/modules/module-protocol-stub.c
++++ b/src/modules/module-protocol-stub.c
+@@ -300,7 +300,9 @@ int pa__init(pa_module*m) {
+
+ # if defined(USE_PROTOCOL_ESOUND)
+
+-# if defined(USE_PER_USER_ESOUND_SOCKET)
++ /* Windows doesn't support getuid(), so we ignore the per-user Esound socket compile flag.
++ * Moreover, Esound Unix sockets haven't been supported on Windows historically. */
++# if defined(USE_PER_USER_ESOUND_SOCKET) && !defined(OS_IS_WIN32)
+ u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid());
+ # else
+ u->socket_path = pa_xstrdup("/tmp/.esd/socket");
+diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
+index 038aca114..ec9eb875f 100644
+--- a/src/modules/module-solaris.c
++++ b/src/modules/module-solaris.c
+@@ -714,9 +714,7 @@ static void thread_func(void *userdata) {
+ pa_memblock_release(u->memchunk.memblock);
+
+ if (w <= 0) {
+- if (errno == EINTR) {
+- continue;
+- } else if (errno == EAGAIN) {
++ if (errno == EAGAIN) {
+ /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */
+ pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes);
+ break;
+diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
+index 7144a664b..d26543bde 100644
+--- a/src/modules/module-stream-restore.c
++++ b/src/modules/module-stream-restore.c
+@@ -1029,6 +1029,8 @@ static struct entry *legacy_entry_read(struct userdata *u, const char *name) {
+ pa_datum data;
+ struct legacy_entry *le;
+ struct entry *e;
++ pa_channel_map channel_map;
++ pa_cvolume volume;
+
+ pa_assert(u);
+ pa_assert(name);
+@@ -1073,12 +1075,17 @@ static struct entry *legacy_entry_read(struct userdata *u, const char *name) {
+ goto fail;
+ }
+
+- if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) {
++ /* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these
++ * members to be accessed directly */
++ channel_map = le->channel_map;
++ volume = le->volume;
++
++ if (le->volume_valid && !pa_channel_map_valid(&channel_map)) {
+ pa_log_warn("Invalid channel map stored in database for legacy stream");
+ goto fail;
+ }
+
+- if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) {
++ if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) {
+ pa_log_warn("Invalid volume stored in database for legacy stream");
+ goto fail;
+ }
+diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
+index 84b856659..f450004ca 100644
+--- a/src/modules/module-switch-on-port-available.c
++++ b/src/modules/module-switch-on-port-available.c
+@@ -228,9 +228,7 @@ static struct port_pointers find_port_pointers(pa_device_port *port) {
+ }
+
+ /* Switches to a port, switching profiles if necessary or preferred */
+-static void switch_to_port(pa_device_port *port) {
+- struct port_pointers pp = find_port_pointers(port);
+-
++static void switch_to_port(pa_device_port *port, struct port_pointers pp) {
+ if (pp.is_port_active)
+ return; /* Already selected */
+
+@@ -252,8 +250,7 @@ static void switch_to_port(pa_device_port *port) {
+ }
+
+ /* Switches away from a port, switching profiles if necessary or preferred */
+-static void switch_from_port(pa_device_port *port) {
+- struct port_pointers pp = find_port_pointers(port);
++static void switch_from_port(pa_device_port *port, struct port_pointers pp) {
+ pa_device_port *p, *best_port = NULL;
+ void *state;
+
+@@ -282,12 +279,12 @@ static void switch_from_port(pa_device_port *port) {
+ * PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED callback, as at this point
+ * the profile availability hasn't been updated yet. */
+ if (best_port)
+- switch_to_port(best_port);
++ switch_to_port(best_port, pp);
+ }
+
+
+ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
+- pa_assert(port);
++ struct port_pointers pp = find_port_pointers(port);
+
+ if (!port->card) {
+ pa_log_warn("Port %s does not have a card", port->name);
+@@ -314,6 +311,15 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
+ * functionality for setups that can't trigger this kind of
+ * interaction.
+ *
++ * For headset or microphone, if they are part of some availability group
++ * and they become unknown from off, it needs to check if their source is
++ * unlinked or not, if their source is unlinked, let switch_to_port()
++ * process them, then with the running of pa_card_set_profile(), their
++ * source will be created, otherwise the headset or microphone can't be used
++ * to record sound since there is no source for these 2 ports. This issue
++ * is observed on Dell machines which have multi-function audio jack but no
++ * internal mic.
++ *
+ * We should make this configurable so that users can optionally
+ * override the default to a headset or mic. */
+
+@@ -323,20 +329,22 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
+ break;
+ }
+
+- /* For no we only switch the headphone port */
+- if (port->direction != PA_DIRECTION_OUTPUT) {
++ /* Switch the headphone port, the input ports without source and the
++ * input ports their source->active_port is part of a group of ports.
++ */
++ if (port->direction == PA_DIRECTION_INPUT && pp.source && !pp.source->active_port->availability_group) {
+ pa_log_debug("Not switching to input port %s, its availability is unknown.", port->name);
+ break;
+ }
+
+- switch_to_port(port);
++ switch_to_port(port, pp);
+ break;
+
+ case PA_AVAILABLE_YES:
+- switch_to_port(port);
++ switch_to_port(port, pp);
+ break;
+ case PA_AVAILABLE_NO:
+- switch_from_port(port);
++ switch_from_port(port, pp);
+ break;
+ default:
+ break;
+diff --git a/src/modules/module-tunnel-sink-new.c b/src/modules/module-tunnel-sink-new.c
+index 802e6a59a..66a178717 100644
+--- a/src/modules/module-tunnel-sink-new.c
++++ b/src/modules/module-tunnel-sink-new.c
+@@ -280,6 +280,9 @@ static void stream_changed_buffer_attr_cb(pa_stream *stream, void *userdata) {
+
+ bufferattr = pa_stream_get_buffer_attr(u->stream);
+ pa_sink_set_max_request_within_thread(u->sink, bufferattr->tlength);
++
++ pa_log_debug("Server reports buffer attrs changed. tlength now at %lu.",
++ (unsigned long) bufferattr->tlength);
+ }
+
+ /* called after we requested a change of the stream buffer_attr */
+@@ -287,6 +290,16 @@ static void stream_set_buffer_attr_cb(pa_stream *stream, int success, void *user
+ stream_changed_buffer_attr_cb(stream, userdata);
+ }
+
++/* called when the server experiences an underrun of our buffer */
++static void stream_underflow_callback(pa_stream *stream, void *userdata) {
++ pa_log_info("Server signalled buffer underrun.");
++}
++
++/* called when the server experiences an overrun of our buffer */
++static void stream_overflow_callback(pa_stream *stream, void *userdata) {
++ pa_log_info("Server signalled buffer overrun.");
++}
++
+ static void context_state_cb(pa_context *c, void *userdata) {
+ struct userdata *u = userdata;
+ pa_assert(u);
+@@ -333,8 +346,12 @@ static void context_state_cb(pa_context *c, void *userdata) {
+ reset_bufferattr(&bufferattr);
+ bufferattr.tlength = pa_usec_to_bytes(requested_latency, &u->sink->sample_spec);
+
++ pa_log_debug("tlength requested at %lu.", (unsigned long) bufferattr.tlength);
++
+ pa_stream_set_state_callback(u->stream, stream_state_cb, userdata);
+ pa_stream_set_buffer_attr_callback(u->stream, stream_changed_buffer_attr_cb, userdata);
++ pa_stream_set_underflow_callback(u->stream, stream_underflow_callback, userdata);
++ pa_stream_set_overflow_callback(u->stream, stream_overflow_callback, userdata);
+ if (pa_stream_connect_playback(u->stream,
+ u->remote_sink_name,
+ &bufferattr,
+@@ -383,6 +400,9 @@ static void sink_update_requested_latency_cb(pa_sink *s) {
+ if (pa_stream_get_buffer_attr(u->stream)->tlength == nbytes)
+ break;
+
++ pa_log_debug("Requesting new buffer attrs. tlength requested at %lu.",
++ (unsigned long) nbytes);
++
+ reset_bufferattr(&bufferattr);
+ bufferattr.tlength = nbytes;
+ if ((operation = pa_stream_set_buffer_attr(u->stream, &bufferattr, stream_set_buffer_attr_cb, u)))
+diff --git a/src/modules/module-virtual-surround-sink.c b/src/modules/module-virtual-surround-sink.c
+index c32107388..0506370e8 100644
+--- a/src/modules/module-virtual-surround-sink.c
++++ b/src/modules/module-virtual-surround-sink.c
+@@ -4,6 +4,8 @@
+ Copyright 2010 Intel Corporation
+ Contributor: Pierre-Louis Bossart
+ Copyright 2012 Niels Ole Salscheider
++ Contributor: Alexander E. Patrakov
++ Copyright 2020 Christopher Snowhill
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+@@ -23,6 +25,10 @@
+ #include
+ #endif
+
++#include
++
++#include
++
+ #include
+ #include
+
+@@ -39,9 +45,8 @@
+ #include
+ #include
+
+-#include
+
+-PA_MODULE_AUTHOR("Niels Ole Salscheider");
++PA_MODULE_AUTHOR("Christopher Snowhill");
+ PA_MODULE_DESCRIPTION(_("Virtual surround sink"));
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+ PA_MODULE_LOAD_ONCE(false);
+@@ -57,6 +62,8 @@ PA_MODULE_USAGE(
+ "use_volume_sharing= "
+ "force_flat_volume= "
+ "hrir=/path/to/left_hrir.wav "
++ "hrir_left=/path/to/left_hrir.wav "
++ "hrir_right=/path/to/optional/right_hrir.wav "
+ "autoloaded= "
+ ));
+
+@@ -66,32 +73,26 @@ PA_MODULE_USAGE(
+ struct userdata {
+ pa_module *module;
+
+- /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */
+- /* bool autoloaded; */
++ bool autoloaded;
+
+ pa_sink *sink;
+ pa_sink_input *sink_input;
+
+- pa_memblockq *memblockq;
++ pa_memblockq *memblockq_sink;
+
+ bool auto_desc;
+- unsigned channels;
+- unsigned hrir_channels;
+
+- unsigned fs, sink_fs;
++ size_t fftlen;
++ size_t hrir_samples;
++ size_t inputs;
+
+- unsigned *mapping_left;
+- unsigned *mapping_right;
+-
+- unsigned hrir_samples;
+- float *hrir_data;
+-
+- float *input_buffer;
+- int input_buffer_offset;
+-
+- bool autoloaded;
++ fftwf_plan *p_fw, p_bw;
++ fftwf_complex *f_in, *f_out, **f_ir;
++ float *revspace, *outspace[2], **inspace;
+ };
+
++#define BLOCK_SIZE (512)
++
+ static const char* const valid_modargs[] = {
+ "sink_name",
+ "sink_properties",
+@@ -103,11 +104,157 @@ static const char* const valid_modargs[] = {
+ "channel_map",
+ "use_volume_sharing",
+ "force_flat_volume",
+- "hrir",
+ "autoloaded",
++ "hrir",
++ "hrir_left",
++ "hrir_right",
+ NULL
+ };
+
++/* Vector size of 4 floats */
++#define v_size 4
++static void * alloc(size_t x, size_t s) {
++ size_t f;
++ float *t;
++
++ f = PA_ROUND_UP(x*s, sizeof(float)*v_size);
++ pa_assert_se(t = fftwf_malloc(f));
++ pa_memzero(t, f);
++
++ return t;
++}
++
++static size_t sink_input_samples(size_t nbytes)
++{
++ return nbytes / 8;
++}
++
++static size_t sink_input_bytes(size_t nsamples)
++{
++ return nsamples * 8;
++}
++
++static size_t sink_samples(const struct userdata *u, size_t nbytes)
++{
++ return nbytes / (u->inputs * 4);
++}
++
++static size_t sink_bytes(const struct userdata *u, size_t nsamples)
++{
++ return nsamples * (u->inputs * 4);
++}
++
++/* Mirror channels for symmetrical impulse */
++static pa_channel_position_t mirror_channel(pa_channel_position_t channel) {
++ switch (channel) {
++ case PA_CHANNEL_POSITION_FRONT_LEFT:
++ return PA_CHANNEL_POSITION_FRONT_RIGHT;
++
++ case PA_CHANNEL_POSITION_FRONT_RIGHT:
++ return PA_CHANNEL_POSITION_FRONT_LEFT;
++
++ case PA_CHANNEL_POSITION_REAR_LEFT:
++ return PA_CHANNEL_POSITION_REAR_RIGHT;
++
++ case PA_CHANNEL_POSITION_REAR_RIGHT:
++ return PA_CHANNEL_POSITION_REAR_LEFT;
++
++ case PA_CHANNEL_POSITION_SIDE_LEFT:
++ return PA_CHANNEL_POSITION_SIDE_RIGHT;
++
++ case PA_CHANNEL_POSITION_SIDE_RIGHT:
++ return PA_CHANNEL_POSITION_SIDE_LEFT;
++
++ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
++ return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
++
++ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
++ return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
++
++ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
++ return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
++
++ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
++ return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
++
++ case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
++ return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
++
++ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
++ return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
++
++ default:
++ return channel;
++ }
++}
++
++/* Normalize the hrir */
++static void normalize_hrir(float * hrir_data, unsigned hrir_samples, unsigned hrir_channels) {
++ /* normalize hrir to avoid audible clipping
++ *
++ * The following heuristic tries to avoid audible clipping. It cannot avoid
++ * clipping in the worst case though, because the scaling factor would
++ * become too large resulting in a too quiet signal.
++ * The idea of the heuristic is to avoid clipping when a single click is
++ * played back on all channels. The scaling factor describes the additional
++ * factor that is necessary to avoid clipping for "normal" signals.
++ *
++ * This algorithm doesn't pretend to be perfect, it's just something that
++ * appears to work (not too quiet, no audible clipping) on the material that
++ * it has been tested on. If you find a real-world example where this
++ * algorithm results in audible clipping, please write a patch that adjusts
++ * the scaling factor constants or improves the algorithm (or if you can't
++ * write a patch, at least report the problem to the PulseAudio mailing list
++ * or bug tracker). */
++
++ const float scaling_factor = 2.5;
++
++ float hrir_sum, hrir_max;
++ unsigned i, j;
++
++ hrir_max = 0;
++ for (i = 0; i < hrir_samples; i++) {
++ hrir_sum = 0;
++ for (j = 0; j < hrir_channels; j++)
++ hrir_sum += fabs(hrir_data[i * hrir_channels + j]);
++
++ if (hrir_sum > hrir_max)
++ hrir_max = hrir_sum;
++ }
++
++ for (i = 0; i < hrir_samples; i++) {
++ for (j = 0; j < hrir_channels; j++)
++ hrir_data[i * hrir_channels + j] /= hrir_max * scaling_factor;
++ }
++}
++
++/* Normalize a stereo hrir */
++static void normalize_hrir_stereo(float * hrir_data, float * hrir_right_data, unsigned hrir_samples, unsigned hrir_channels) {
++ const float scaling_factor = 2.5;
++
++ float hrir_sum, hrir_max;
++ unsigned i, j;
++
++ hrir_max = 0;
++ for (i = 0; i < hrir_samples; i++) {
++ hrir_sum = 0;
++ for (j = 0; j < hrir_channels; j++) {
++ hrir_sum += fabs(hrir_data[i * hrir_channels + j]);
++ hrir_sum += fabs(hrir_right_data[i * hrir_channels + j]);
++ }
++
++ if (hrir_sum > hrir_max)
++ hrir_max = hrir_sum;
++ }
++
++ for (i = 0; i < hrir_samples; i++) {
++ for (j = 0; j < hrir_channels; j++) {
++ hrir_data[i * hrir_channels + j] /= hrir_max * scaling_factor;
++ hrir_right_data[i * hrir_channels + j] /= hrir_max * scaling_factor;
++ }
++ }
++}
++
+ /* Called from I/O thread context */
+ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+@@ -121,11 +268,11 @@ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t of
+ * sink input is first shut down, the sink second. */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+- *((int64_t*) data) = 0;
++ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+- *((int64_t*) data) =
++ *((pa_usec_t*) data) =
+
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
+@@ -174,6 +321,7 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
+ /* Called from I/O thread context */
+ static void sink_request_rewind_cb(pa_sink *s) {
+ struct userdata *u;
++ size_t nbytes_sink, nbytes_input;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+@@ -182,10 +330,11 @@ static void sink_request_rewind_cb(pa_sink *s) {
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
++ nbytes_sink = s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq_sink);
++ nbytes_input = sink_input_bytes(sink_samples(u, nbytes_sink));
++
+ /* Just hand this one over to the master sink */
+- pa_sink_input_request_rewind(u->sink_input,
+- s->thread_info.rewind_nbytes +
+- pa_memblockq_get_length(u->memblockq), true, false, false);
++ pa_sink_input_request_rewind(u->sink_input, nbytes_input, true, false, false);
+ }
+
+ /* Called from I/O thread context */
+@@ -233,136 +382,177 @@ static void sink_set_mute_cb(pa_sink *s) {
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+ }
+
++static size_t memblockq_missing(pa_memblockq *bq) {
++ size_t l, tlength;
++ pa_assert(bq);
++
++ tlength = pa_memblockq_get_tlength(bq);
++ if ((l = pa_memblockq_get_length(bq)) >= tlength)
++ return 0;
++
++ l = tlength - l;
++ return l >= pa_memblockq_get_minreq(bq) ? l : 0;
++}
++
+ /* Called from I/O thread context */
+-static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
++static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes_input, pa_memchunk *chunk) {
+ struct userdata *u;
+ float *src, *dst;
+- unsigned n;
++ int c, ear;
++ size_t s, bytes_missing, fftlen;
+ pa_memchunk tchunk;
+-
+- unsigned j, k, l;
+- float sum_right, sum_left;
+- float current_sample;
++ float fftlen_if, *revspace;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(chunk);
+ pa_assert_se(u = i->userdata);
+
+- if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
+- return -1;
+-
+ /* Hmm, process any rewind request that might be queued up */
+ pa_sink_process_rewind(u->sink, 0);
+
+- while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
++ while ((bytes_missing = memblockq_missing(u->memblockq_sink)) != 0) {
+ pa_memchunk nchunk;
+
+- pa_sink_render(u->sink, nbytes * u->sink_fs / u->fs, &nchunk);
+- pa_memblockq_push(u->memblockq, &nchunk);
++ pa_sink_render(u->sink, bytes_missing, &nchunk);
++ pa_memblockq_push(u->memblockq_sink, &nchunk);
+ pa_memblock_unref(nchunk.memblock);
+ }
+
+- tchunk.length = PA_MIN(nbytes * u->sink_fs / u->fs, tchunk.length);
+- pa_assert(tchunk.length > 0);
++ pa_memblockq_rewind(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE));
++ pa_memblockq_peek_fixed_size(u->memblockq_sink, sink_bytes(u, u->fftlen), &tchunk);
+
+- n = (unsigned) (tchunk.length / u->sink_fs);
++ pa_memblockq_drop(u->memblockq_sink, tchunk.length);
+
+- pa_assert(n > 0);
++ /* Now tchunk contains enough data to perform the FFT
++ * This should be equal to u->fftlen */
+
+ chunk->index = 0;
+- chunk->length = n * u->fs;
++ chunk->length = sink_input_bytes(BLOCK_SIZE);
+ chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
+
+- pa_memblockq_drop(u->memblockq, n * u->sink_fs);
+-
+ src = pa_memblock_acquire_chunk(&tchunk);
+- dst = pa_memblock_acquire(chunk->memblock);
+
+- for (l = 0; l < n; l++) {
+- memcpy(((char*) u->input_buffer) + u->input_buffer_offset * u->sink_fs, ((char *) src) + l * u->sink_fs, u->sink_fs);
++ for (c = 0; c < u->inputs; c++) {
++ for (s = 0, fftlen = u->fftlen; s < fftlen; s++) {
++ u->inspace[c][s] = src[s * u->inputs + c];
++ }
++ }
++
++ pa_memblock_release(tchunk.memblock);
++ pa_memblock_unref(tchunk.memblock);
++
++ fftlen_if = 1.0f / (float)u->fftlen;
++ revspace = u->revspace + u->fftlen - BLOCK_SIZE;
++
++ pa_memzero(u->outspace[0], BLOCK_SIZE * 4);
++ pa_memzero(u->outspace[1], BLOCK_SIZE * 4);
++
++ for (c = 0; c < u->inputs; c++) {
++ fftwf_complex *f_in = u->f_in;
++ fftwf_complex *f_out = u->f_out;
+
+- sum_right = 0;
+- sum_left = 0;
++ fftwf_execute(u->p_fw[c]);
+
+- /* fold the input buffer with the impulse response */
+- for (j = 0; j < u->hrir_samples; j++) {
+- for (k = 0; k < u->channels; k++) {
+- current_sample = u->input_buffer[((u->input_buffer_offset + j) % u->hrir_samples) * u->channels + k];
++ for (ear = 0; ear < 2; ear++) {
++ fftwf_complex *f_ir = u->f_ir[c * 2 + ear];
++ float *outspace = u->outspace[ear];
+
+- sum_left += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_left[k]];
+- sum_right += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_right[k]];
++ for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) {
++ float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1];
++ float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1];
++ f_out[s][0] = re;
++ f_out[s][1] = im;
+ }
++
++ fftwf_execute(u->p_bw);
++
++ for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s)
++ outspace[s] += revspace[s] * fftlen_if;
+ }
++ }
++
++ dst = pa_memblock_acquire_chunk(chunk);
++
++ for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) {
++ float output;
++ float *outspace = u->outspace[0];
+
+- dst[2 * l] = PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f);
+- dst[2 * l + 1] = PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f);
++ output = outspace[s];
++ if (output < -1.0) output = -1.0;
++ if (output > 1.0) output = 1.0;
++ dst[s * 2 + 0] = output;
+
+- u->input_buffer_offset--;
+- if (u->input_buffer_offset < 0)
+- u->input_buffer_offset += u->hrir_samples;
++ outspace = u->outspace[1];
++
++ output = outspace[s];
++ if (output < -1.0) output = -1.0;
++ if (output > 1.0) output = 1.0;
++ dst[s * 2 + 1] = output;
+ }
+
+- pa_memblock_release(tchunk.memblock);
+ pa_memblock_release(chunk->memblock);
+
+- pa_memblock_unref(tchunk.memblock);
+-
+ return 0;
+ }
+
+ /* Called from I/O thread context */
+-static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
++static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes_input) {
+ struct userdata *u;
+ size_t amount = 0;
++ size_t nbytes_sink;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+- /* If the sink is not yet linked, there is nothing to rewind */
+- if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
+- return;
++ nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
+
+ if (u->sink->thread_info.rewind_nbytes > 0) {
+ size_t max_rewrite;
+
+- max_rewrite = nbytes * u->sink_fs / u->fs + pa_memblockq_get_length(u->memblockq);
+- amount = PA_MIN(u->sink->thread_info.rewind_nbytes * u->sink_fs / u->fs, max_rewrite);
++ max_rewrite = nbytes_sink + pa_memblockq_get_length(u->memblockq_sink);
++ amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
+ u->sink->thread_info.rewind_nbytes = 0;
+
+ if (amount > 0) {
+- pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true);
+-
+- /* Reset the input buffer */
+- memset(u->input_buffer, 0, u->hrir_samples * u->sink_fs);
+- u->input_buffer_offset = 0;
++ pa_memblockq_seek(u->memblockq_sink, - (int64_t) amount, PA_SEEK_RELATIVE, true);
+ }
+ }
+
+ pa_sink_process_rewind(u->sink, amount);
+- pa_memblockq_rewind(u->memblockq, nbytes * u->sink_fs / u->fs);
++
++ pa_memblockq_rewind(u->memblockq_sink, nbytes_sink);
+ }
+
+ /* Called from I/O thread context */
+-static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
++static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes_input) {
+ struct userdata *u;
++ size_t nbytes_sink, nbytes_memblockq;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
++ nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
++ nbytes_memblockq = sink_bytes(u, sink_input_samples(nbytes_input) + u->fftlen);
++
+ /* FIXME: Too small max_rewind:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
+- pa_memblockq_set_maxrewind(u->memblockq, nbytes * u->sink_fs / u->fs);
+- pa_sink_set_max_rewind_within_thread(u->sink, nbytes * u->sink_fs / u->fs);
++ pa_memblockq_set_maxrewind(u->memblockq_sink, nbytes_memblockq);
++ pa_sink_set_max_rewind_within_thread(u->sink, nbytes_sink);
+ }
+
+ /* Called from I/O thread context */
+-static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
++static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes_input) {
+ struct userdata *u;
+
++ size_t nbytes_sink;
++
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+- pa_sink_set_max_request_within_thread(u->sink, nbytes * u->sink_fs / u->fs);
++ nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
++
++ nbytes_sink = PA_ROUND_UP(nbytes_sink, sink_bytes(u, BLOCK_SIZE));
++ pa_sink_set_max_request_within_thread(u->sink, nbytes_sink);
+ }
+
+ /* Called from I/O thread context */
+@@ -401,6 +591,7 @@ static void sink_input_detach_cb(pa_sink_input *i) {
+ /* Called from I/O thread context */
+ static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
++ size_t max_request;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+@@ -410,14 +601,15 @@ static void sink_input_attach_cb(pa_sink_input *i) {
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+
+- pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i) * u->sink_fs / u->fs);
++ max_request = sink_bytes(u, sink_input_samples(pa_sink_input_get_max_request(i)));
++ max_request = PA_ROUND_UP(max_request, sink_bytes(u, BLOCK_SIZE));
++ pa_sink_set_max_request_within_thread(u->sink, max_request);
+
+ /* FIXME: Too small max_rewind:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
+- pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i) * u->sink_fs / u->fs);
++ pa_sink_set_max_rewind_within_thread(u->sink, sink_bytes(u, sink_input_samples(pa_sink_input_get_max_rewind(i))));
+
+- if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
+- pa_sink_attach_within_thread(u->sink);
++ pa_sink_attach_within_thread(u->sink);
+ }
+
+ /* Called from main context */
+@@ -427,12 +619,12 @@ static void sink_input_kill_cb(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+- /* The order here matters! We first kill the sink so that streams
+- * can properly be moved away while the sink input is still connected
+- * to the master. */
++ /* The order here matters! We first kill the sink input, followed
++ * by the sink. That means the sink callbacks must be protected
++ * against an unconnected sink input! */
+ pa_sink_input_cork(u->sink_input, true);
+- pa_sink_unlink(u->sink);
+ pa_sink_input_unlink(u->sink_input);
++ pa_sink_unlink(u->sink);
+
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+@@ -503,119 +695,57 @@ static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ pa_sink_mute_changed(u->sink, i->muted);
+ }
+
+-static pa_channel_position_t mirror_channel(pa_channel_position_t channel) {
+- switch (channel) {
+- case PA_CHANNEL_POSITION_FRONT_LEFT:
+- return PA_CHANNEL_POSITION_FRONT_RIGHT;
+-
+- case PA_CHANNEL_POSITION_FRONT_RIGHT:
+- return PA_CHANNEL_POSITION_FRONT_LEFT;
+-
+- case PA_CHANNEL_POSITION_REAR_LEFT:
+- return PA_CHANNEL_POSITION_REAR_RIGHT;
+-
+- case PA_CHANNEL_POSITION_REAR_RIGHT:
+- return PA_CHANNEL_POSITION_REAR_LEFT;
+-
+- case PA_CHANNEL_POSITION_SIDE_LEFT:
+- return PA_CHANNEL_POSITION_SIDE_RIGHT;
+-
+- case PA_CHANNEL_POSITION_SIDE_RIGHT:
+- return PA_CHANNEL_POSITION_SIDE_LEFT;
+-
+- case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
+- return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+-
+- case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
+- return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+-
+- case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
+- return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+-
+- case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
+- return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+-
+- case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
+- return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+-
+- case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
+- return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+-
+- default:
+- return channel;
+- }
+-}
+-
+-static void normalize_hrir(struct userdata *u) {
+- /* normalize hrir to avoid audible clipping
+- *
+- * The following heuristic tries to avoid audible clipping. It cannot avoid
+- * clipping in the worst case though, because the scaling factor would
+- * become too large resulting in a too quiet signal.
+- * The idea of the heuristic is to avoid clipping when a single click is
+- * played back on all channels. The scaling factor describes the additional
+- * factor that is necessary to avoid clipping for "normal" signals.
+- *
+- * This algorithm doesn't pretend to be perfect, it's just something that
+- * appears to work (not too quiet, no audible clipping) on the material that
+- * it has been tested on. If you find a real-world example where this
+- * algorithm results in audible clipping, please write a patch that adjusts
+- * the scaling factor constants or improves the algorithm (or if you can't
+- * write a patch, at least report the problem to the PulseAudio mailing list
+- * or bug tracker). */
+-
+- const float scaling_factor = 2.5;
+-
+- float hrir_sum, hrir_max;
+- unsigned i, j;
+-
+- hrir_max = 0;
+- for (i = 0; i < u->hrir_samples; i++) {
+- hrir_sum = 0;
+- for (j = 0; j < u->hrir_channels; j++)
+- hrir_sum += fabs(u->hrir_data[i * u->hrir_channels + j]);
+-
+- if (hrir_sum > hrir_max)
+- hrir_max = hrir_sum;
+- }
+-
+- for (i = 0; i < u->hrir_samples; i++) {
+- for (j = 0; j < u->hrir_channels; j++)
+- u->hrir_data[i * u->hrir_channels + j] /= hrir_max * scaling_factor;
+- }
+-}
+-
+ int pa__init(pa_module*m) {
+ struct userdata *u;
+- pa_sample_spec ss, sink_input_ss;
+- pa_channel_map map, sink_input_map;
++ pa_sample_spec ss_input, ss_output;
++ pa_channel_map map_output;
+ pa_modargs *ma;
+ const char *master_name;
+- pa_sink *master = NULL;
++ const char *hrir_left_file;
++ const char *hrir_right_file;
++ pa_sink *master=NULL;
+ pa_sink_input_new_data sink_input_data;
+ pa_sink_new_data sink_data;
+ bool use_volume_sharing = true;
+ bool force_flat_volume = false;
+ pa_memchunk silence;
++ const char* z;
++ unsigned i, j, ear, found_channel_left, found_channel_right;
++
++ pa_sample_spec ss;
++ pa_channel_map map;
++
++ float *hrir_data=NULL, *hrir_right_data=NULL;
++ float *hrir_temp_data;
++ size_t hrir_samples;
++ size_t hrir_copied_length, hrir_total_length;
++ int hrir_channels;
++ int fftlen;
++
++ float *impulse_temp=NULL;
+
+- const char *hrir_file;
+- unsigned i, j, found_channel_left, found_channel_right;
+- float *hrir_data;
++ unsigned *mapping_left=NULL;
++ unsigned *mapping_right=NULL;
+
+- pa_sample_spec hrir_ss;
+- pa_channel_map hrir_map;
++ fftwf_plan p;
+
+- pa_sample_spec hrir_temp_ss;
+- pa_memchunk hrir_temp_chunk, hrir_temp_chunk_resampled;
++ pa_channel_map hrir_map, hrir_right_map;
++
++ pa_sample_spec hrir_left_temp_ss;
++ pa_memchunk hrir_left_temp_chunk, hrir_left_temp_chunk_resampled;
+ pa_resampler *resampler;
+
+- size_t hrir_copied_length, hrir_total_length;
+
+- hrir_temp_chunk.memblock = NULL;
+- hrir_temp_chunk_resampled.memblock = NULL;
++ pa_sample_spec hrir_right_temp_ss;
++ pa_memchunk hrir_right_temp_chunk, hrir_right_temp_chunk_resampled;
+
+ pa_assert(m);
+
++ hrir_left_temp_chunk.memblock = NULL;
++ hrir_left_temp_chunk_resampled.memblock = NULL;
++ hrir_right_temp_chunk.memblock = NULL;
++ hrir_right_temp_chunk_resampled.memblock = NULL;
++
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+@@ -629,45 +759,61 @@ int pa__init(pa_module*m) {
+ "please use the 'sink_master' argument instead.");
+ }
+
+- master = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK);
+- if (!master) {
+- pa_log("Master sink not found.");
++ if (!(master = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK))) {
++ pa_log("Master sink not found");
+ goto fail;
+ }
+
+- pa_assert(master);
++ hrir_left_file = pa_modargs_get_value(ma, "hrir_left", NULL);
++ if (!hrir_left_file) {
++ hrir_left_file = pa_modargs_get_value(ma, "hrir", NULL);
++ if (!hrir_left_file) {
++ pa_log("Either the 'hrir' or 'hrir_left' module arguments are required.");
++ goto fail;
++ }
++ }
+
+- u = pa_xnew0(struct userdata, 1);
+- u->module = m;
+- m->userdata = u;
++ hrir_right_file = pa_modargs_get_value(ma, "hrir_right", NULL);
+
+- /* Initialize hrir and input buffer */
+- /* this is the hrir file for the left ear! */
+- if (!(hrir_file = pa_modargs_get_value(ma, "hrir", NULL))) {
+- pa_log("The mandatory 'hrir' module argument is missing.");
+- goto fail;
+- }
++ pa_assert(master);
+
+- if (pa_sound_file_load(master->core->mempool, hrir_file, &hrir_temp_ss, &hrir_map, &hrir_temp_chunk, NULL) < 0) {
++ if (pa_sound_file_load(master->core->mempool, hrir_left_file, &hrir_left_temp_ss, &hrir_map, &hrir_left_temp_chunk, NULL) < 0) {
+ pa_log("Cannot load hrir file.");
+ goto fail;
+ }
+
+- /* sample spec / map of hrir */
+- hrir_ss.format = PA_SAMPLE_FLOAT32;
+- hrir_ss.rate = master->sample_spec.rate;
+- hrir_ss.channels = hrir_temp_ss.channels;
++ if (hrir_right_file) {
++ if (pa_sound_file_load(master->core->mempool, hrir_right_file, &hrir_right_temp_ss, &hrir_right_map, &hrir_right_temp_chunk, NULL) < 0) {
++ pa_log("Cannot load hrir_right file.");
++ goto fail;
++ }
++ if (!pa_sample_spec_equal(&hrir_left_temp_ss, &hrir_right_temp_ss)) {
++ pa_log("Both hrir_left and hrir_right must have the same sample format");
++ goto fail;
++ }
++ if (!pa_channel_map_equal(&hrir_map, &hrir_right_map)) {
++ pa_log("Both hrir_left and hrir_right must have the same channel layout");
++ goto fail;
++ }
++ }
++
++ ss_input.format = PA_SAMPLE_FLOAT32NE;
++ ss_input.rate = master->sample_spec.rate;
++ ss_input.channels = hrir_left_temp_ss.channels;
+
+- /* sample spec of sink */
+- ss = hrir_ss;
++ ss = ss_input;
+ map = hrir_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+- ss.format = PA_SAMPLE_FLOAT32;
+- hrir_ss.rate = ss.rate;
+- u->channels = ss.channels;
++
++ ss.format = PA_SAMPLE_FLOAT32NE;
++ ss_input.rate = ss.rate;
++ ss_input.channels = ss.channels;
++
++ ss_output = ss_input;
++ ss_output.channels = 2;
+
+ if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) {
+ pa_log("use_volume_sharing= expects a boolean argument");
+@@ -684,14 +830,11 @@ int pa__init(pa_module*m) {
+ goto fail;
+ }
+
+- /* sample spec / map of sink input */
+- pa_channel_map_init_stereo(&sink_input_map);
+- sink_input_ss.channels = 2;
+- sink_input_ss.format = PA_SAMPLE_FLOAT32;
+- sink_input_ss.rate = ss.rate;
++ pa_channel_map_init_stereo(&map_output);
+
+- u->sink_fs = pa_frame_size(&ss);
+- u->fs = pa_frame_size(&sink_input_ss);
++ u = pa_xnew0(struct userdata, 1);
++ u->module = m;
++ m->userdata = u;
+
+ /* Create sink */
+ pa_sink_new_data_init(&sink_data);
+@@ -699,7 +842,7 @@ int pa__init(pa_module*m) {
+ sink_data.module = m;
+ if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+ sink_data.name = pa_sprintf_malloc("%s.vsurroundsink", master->name);
+- pa_sink_new_data_set_sample_spec(&sink_data, &ss);
++ pa_sink_new_data_set_sample_spec(&sink_data, &ss_input);
+ pa_sink_new_data_set_channel_map(&sink_data, &map);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+@@ -718,8 +861,6 @@ int pa__init(pa_module*m) {
+ }
+
+ if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+- const char *z;
+-
+ z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s", sink_data.name, z ? z : master->name);
+ }
+@@ -743,7 +884,7 @@ int pa__init(pa_module*m) {
+ pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+ pa_sink_enable_decibel_volume(u->sink, true);
+ }
+- /* Normally this flag would be enabled automatically be we can force it. */
++ /* Normally this flag would be enabled automatically but we can force it. */
+ if (force_flat_volume)
+ u->sink->flags |= PA_SINK_FLAT_VOLUME;
+ u->sink->userdata = u;
+@@ -758,9 +899,8 @@ int pa__init(pa_module*m) {
+ sink_input_data.origin_sink = u->sink;
+ pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Surround Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+- pa_sink_input_new_data_set_sample_spec(&sink_input_data, &sink_input_ss);
+- pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_input_map);
+- sink_input_data.flags |= PA_SINK_INPUT_START_CORKED;
++ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss_output);
++ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map_output);
+
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
+ pa_sink_input_new_data_done(&sink_input_data);
+@@ -786,78 +926,107 @@ int pa__init(pa_module*m) {
+ u->sink->input_to_master = u->sink_input;
+
+ pa_sink_input_get_silence(u->sink_input, &silence);
+- u->memblockq = pa_memblockq_new("module-virtual-surround-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
+- pa_memblock_unref(silence.memblock);
+
+- /* resample hrir */
+- resampler = pa_resampler_new(u->sink->core->mempool, &hrir_temp_ss, &hrir_map, &hrir_ss, &hrir_map, u->sink->core->lfe_crossover_freq,
++ resampler = pa_resampler_new(u->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->sink->core->lfe_crossover_freq,
+ PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP);
+
+- u->hrir_samples = hrir_temp_chunk.length / pa_frame_size(&hrir_temp_ss) * hrir_ss.rate / hrir_temp_ss.rate;
+- if (u->hrir_samples > 64) {
+- u->hrir_samples = 64;
+- pa_log("The (resampled) hrir contains more than 64 samples. Only the first 64 samples will be used to limit processor usage.");
+- }
++ hrir_samples = hrir_left_temp_chunk.length / pa_frame_size(&hrir_left_temp_ss) * ss_input.rate / hrir_left_temp_ss.rate;
+
+- hrir_total_length = u->hrir_samples * pa_frame_size(&hrir_ss);
+- u->hrir_channels = hrir_ss.channels;
++ hrir_total_length = hrir_samples * pa_frame_size(&ss_input);
++ hrir_channels = ss_input.channels;
+
+- u->hrir_data = (float *) pa_xmalloc(hrir_total_length);
++ hrir_data = (float *) pa_xmalloc(hrir_total_length);
+ hrir_copied_length = 0;
+
++ u->hrir_samples = hrir_samples;
++ u->inputs = hrir_channels;
++
+ /* add silence to the hrir until we get enough samples out of the resampler */
+ while (hrir_copied_length < hrir_total_length) {
+- pa_resampler_run(resampler, &hrir_temp_chunk, &hrir_temp_chunk_resampled);
+- if (hrir_temp_chunk.memblock != hrir_temp_chunk_resampled.memblock) {
++ pa_resampler_run(resampler, &hrir_left_temp_chunk, &hrir_left_temp_chunk_resampled);
++ if (hrir_left_temp_chunk.memblock != hrir_left_temp_chunk_resampled.memblock) {
+ /* Silence input block */
+- pa_silence_memblock(hrir_temp_chunk.memblock, &hrir_temp_ss);
++ pa_silence_memblock(hrir_left_temp_chunk.memblock, &hrir_left_temp_ss);
+ }
+
+- if (hrir_temp_chunk_resampled.memblock) {
++ if (hrir_left_temp_chunk_resampled.memblock) {
+ /* Copy hrir data */
+- hrir_data = (float *) pa_memblock_acquire(hrir_temp_chunk_resampled.memblock);
++ hrir_temp_data = (float *) pa_memblock_acquire(hrir_left_temp_chunk_resampled.memblock);
+
+- if (hrir_total_length - hrir_copied_length >= hrir_temp_chunk_resampled.length) {
+- memcpy(u->hrir_data + hrir_copied_length, hrir_data, hrir_temp_chunk_resampled.length);
+- hrir_copied_length += hrir_temp_chunk_resampled.length;
++ if (hrir_total_length - hrir_copied_length >= hrir_left_temp_chunk_resampled.length) {
++ memcpy(hrir_data + hrir_copied_length, hrir_temp_data, hrir_left_temp_chunk_resampled.length);
++ hrir_copied_length += hrir_left_temp_chunk_resampled.length;
+ } else {
+- memcpy(u->hrir_data + hrir_copied_length, hrir_data, hrir_total_length - hrir_copied_length);
++ memcpy(hrir_data + hrir_copied_length, hrir_temp_data, hrir_total_length - hrir_copied_length);
+ hrir_copied_length = hrir_total_length;
+ }
+
+- pa_memblock_release(hrir_temp_chunk_resampled.memblock);
+- pa_memblock_unref(hrir_temp_chunk_resampled.memblock);
+- hrir_temp_chunk_resampled.memblock = NULL;
++ pa_memblock_release(hrir_left_temp_chunk_resampled.memblock);
++ pa_memblock_unref(hrir_left_temp_chunk_resampled.memblock);
++ hrir_left_temp_chunk_resampled.memblock = NULL;
+ }
+ }
+
+- pa_resampler_free(resampler);
++ pa_memblock_unref(hrir_left_temp_chunk.memblock);
++ hrir_left_temp_chunk.memblock = NULL;
+
+- pa_memblock_unref(hrir_temp_chunk.memblock);
+- hrir_temp_chunk.memblock = NULL;
++ if (hrir_right_file) {
++ pa_resampler_reset(resampler);
+
+- if (hrir_map.channels < map.channels) {
+- pa_log("hrir file does not have enough channels!");
+- goto fail;
++ hrir_right_data = (float *) pa_xmalloc(hrir_total_length);
++ hrir_copied_length = 0;
++
++ while (hrir_copied_length < hrir_total_length) {
++ pa_resampler_run(resampler, &hrir_right_temp_chunk, &hrir_right_temp_chunk_resampled);
++ if (hrir_right_temp_chunk.memblock != hrir_right_temp_chunk_resampled.memblock) {
++ /* Silence input block */
++ pa_silence_memblock(hrir_right_temp_chunk.memblock, &hrir_right_temp_ss);
++ }
++
++ if (hrir_right_temp_chunk_resampled.memblock) {
++ /* Copy hrir data */
++ hrir_temp_data = (float *) pa_memblock_acquire(hrir_right_temp_chunk_resampled.memblock);
++
++ if (hrir_total_length - hrir_copied_length >= hrir_right_temp_chunk_resampled.length) {
++ memcpy(hrir_right_data + hrir_copied_length, hrir_temp_data, hrir_right_temp_chunk_resampled.length);
++ hrir_copied_length += hrir_right_temp_chunk_resampled.length;
++ } else {
++ memcpy(hrir_right_data + hrir_copied_length, hrir_temp_data, hrir_total_length - hrir_copied_length);
++ hrir_copied_length = hrir_total_length;
++ }
++
++ pa_memblock_release(hrir_right_temp_chunk_resampled.memblock);
++ pa_memblock_unref(hrir_right_temp_chunk_resampled.memblock);
++ hrir_right_temp_chunk_resampled.memblock = NULL;
++ }
++ }
++
++ pa_memblock_unref(hrir_right_temp_chunk.memblock);
++ hrir_right_temp_chunk.memblock = NULL;
+ }
+
+- normalize_hrir(u);
++ pa_resampler_free(resampler);
++
++ if (hrir_right_data)
++ normalize_hrir_stereo(hrir_data, hrir_right_data, hrir_samples, hrir_channels);
++ else
++ normalize_hrir(hrir_data, hrir_samples, hrir_channels);
+
+ /* create mapping between hrir and input */
+- u->mapping_left = (unsigned *) pa_xnew0(unsigned, u->channels);
+- u->mapping_right = (unsigned *) pa_xnew0(unsigned, u->channels);
++ mapping_left = (unsigned *) pa_xnew0(unsigned, hrir_channels);
++ mapping_right = (unsigned *) pa_xnew0(unsigned, hrir_channels);
+ for (i = 0; i < map.channels; i++) {
+ found_channel_left = 0;
+ found_channel_right = 0;
+
+ for (j = 0; j < hrir_map.channels; j++) {
+ if (hrir_map.map[j] == map.map[i]) {
+- u->mapping_left[i] = j;
++ mapping_left[i] = j;
+ found_channel_left = 1;
+ }
+
+ if (hrir_map.map[j] == mirror_channel(map.map[i])) {
+- u->mapping_right[i] = j;
++ mapping_right[i] = j;
+ found_channel_right = 1;
+ }
+ }
+@@ -872,25 +1041,130 @@ int pa__init(pa_module*m) {
+ }
+ }
+
+- u->input_buffer = pa_xmalloc0(u->hrir_samples * u->sink_fs);
+- u->input_buffer_offset = 0;
++ fftlen = (hrir_samples + BLOCK_SIZE + 1); /* Grow a bit for overlap */
++ {
++ /* Round up to a power of two */
++ int pow = 1;
++ while (fftlen > 2) { pow++; fftlen /= 2; }
++ fftlen = 2 << pow;
++ }
++
++ u->fftlen = fftlen;
++
++ u->f_in = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1));
++ u->f_out = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1));
++
++ u->f_ir = (fftwf_complex**) alloc(sizeof(fftwf_complex*), (hrir_channels*2));
++ for (i = 0, j = hrir_channels*2; i < j; i++)
++ u->f_ir[i] = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1));
++
++ u->revspace = (float*) alloc(sizeof(float), fftlen);
++
++ u->outspace[0] = (float*) alloc(sizeof(float), BLOCK_SIZE);
++ u->outspace[1] = (float*) alloc(sizeof(float), BLOCK_SIZE);
++
++ u->inspace = (float**) alloc(sizeof(float*), hrir_channels);
++ for (i = 0; i < hrir_channels; i++)
++ u->inspace[i] = (float*) alloc(sizeof(float), fftlen);
++
++ u->p_fw = (fftwf_plan*) alloc(sizeof(fftwf_plan), hrir_channels);
++ for (i = 0; i < hrir_channels; i++)
++ pa_assert_se(u->p_fw[i] = fftwf_plan_dft_r2c_1d(fftlen, u->inspace[i], u->f_in, FFTW_ESTIMATE));
++
++ pa_assert_se(u->p_bw = fftwf_plan_dft_c2r_1d(fftlen, u->f_out, u->revspace, FFTW_ESTIMATE));
++
++ impulse_temp = (float*) alloc(sizeof(float), fftlen);
++
++ if (hrir_right_data) {
++ for (i = 0; i < hrir_channels; i++) {
++ for (ear = 0; ear < 2; ear++) {
++ size_t index = i * 2 + ear;
++ size_t impulse_index = mapping_left[i];
++ float *impulse = (ear == 0) ? hrir_data : hrir_right_data;
++ for (j = 0; j < hrir_samples; j++) {
++ impulse_temp[j] = impulse[j * hrir_channels + impulse_index];
++ }
++
++ p = fftwf_plan_dft_r2c_1d(fftlen, impulse_temp, u->f_ir[index], FFTW_ESTIMATE);
++ if (p) {
++ fftwf_execute(p);
++ fftwf_destroy_plan(p);
++ } else {
++ pa_log("fftw plan creation failed for %s ear speaker index %d", (ear == 0) ? "left" : "right", i);
++ goto fail;
++ }
++ }
++ }
++ } else {
++ for (i = 0; i < hrir_channels; i++) {
++ for (ear = 0; ear < 2; ear++) {
++ size_t index = i * 2 + ear;
++ size_t impulse_index = (ear == 0) ? mapping_left[i] : mapping_right[i];
++ for (j = 0; j < hrir_samples; j++) {
++ impulse_temp[j] = hrir_data[j * hrir_channels + impulse_index];
++ }
++
++ p = fftwf_plan_dft_r2c_1d(fftlen, impulse_temp, u->f_ir[index], FFTW_ESTIMATE);
++ if (p) {
++ fftwf_execute(p);
++ fftwf_destroy_plan(p);
++ } else {
++ pa_log("fftw plan creation failed for %s ear speaker index %d", (ear == 0) ? "left" : "right", i);
++ goto fail;
++ }
++ }
++ }
++ }
++
++ pa_xfree(impulse_temp);
++
++ pa_xfree(hrir_data);
++ if (hrir_right_data)
++ pa_xfree(hrir_right_data);
++
++ pa_xfree(mapping_left);
++ pa_xfree(mapping_right);
++
++ u->memblockq_sink = pa_memblockq_new("module-virtual-surround-sink memblockq (input)", 0, MEMBLOCKQ_MAXLENGTH, sink_bytes(u, BLOCK_SIZE), &ss_input, 0, 0, sink_bytes(u, u->fftlen), &silence);
++ pa_memblock_unref(silence.memblock);
++
++ pa_memblockq_seek(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE), PA_SEEK_RELATIVE, false);
++ pa_memblockq_flush_read(u->memblockq_sink);
+
+- /* The order here is important. The input must be put first,
+- * otherwise streams might attach to the sink before the sink
+- * input is attached to the master. */
+- pa_sink_input_put(u->sink_input);
+ pa_sink_put(u->sink);
+- pa_sink_input_cork(u->sink_input, false);
++ pa_sink_input_put(u->sink_input);
+
+ pa_modargs_free(ma);
++
+ return 0;
+
+ fail:
+- if (hrir_temp_chunk.memblock)
+- pa_memblock_unref(hrir_temp_chunk.memblock);
++ if (impulse_temp)
++ pa_xfree(impulse_temp);
++
++ if (mapping_left)
++ pa_xfree(mapping_left);
++
++ if (mapping_right)
++ pa_xfree(mapping_right);
++
++ if (hrir_data)
++ pa_xfree(hrir_data);
+
+- if (hrir_temp_chunk_resampled.memblock)
+- pa_memblock_unref(hrir_temp_chunk_resampled.memblock);
++ if (hrir_right_data)
++ pa_xfree(hrir_right_data);
++
++ if (hrir_left_temp_chunk.memblock)
++ pa_memblock_unref(hrir_left_temp_chunk.memblock);
++
++ if (hrir_left_temp_chunk_resampled.memblock)
++ pa_memblock_unref(hrir_left_temp_chunk_resampled.memblock);
++
++ if (hrir_right_temp_chunk.memblock)
++ pa_memblock_unref(hrir_right_temp_chunk.memblock);
++
++ if (hrir_right_temp_chunk_resampled.memblock)
++ pa_memblock_unref(hrir_right_temp_chunk_resampled.memblock);
+
+ if (ma)
+ pa_modargs_free(ma);
+@@ -910,6 +1184,7 @@ int pa__get_n_used(pa_module *m) {
+ }
+
+ void pa__done(pa_module*m) {
++ size_t i, j;
+ struct userdata *u;
+
+ pa_assert(m);
+@@ -921,32 +1196,60 @@ void pa__done(pa_module*m) {
+ * destruction order! */
+
+ if (u->sink_input)
+- pa_sink_input_cork(u->sink_input, true);
++ pa_sink_input_unlink(u->sink_input);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+- if (u->sink_input) {
+- pa_sink_input_unlink(u->sink_input);
++ if (u->sink_input)
+ pa_sink_input_unref(u->sink_input);
+- }
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+- if (u->memblockq)
+- pa_memblockq_free(u->memblockq);
++ if (u->memblockq_sink)
++ pa_memblockq_free(u->memblockq_sink);
+
+- if (u->hrir_data)
+- pa_xfree(u->hrir_data);
++ if (u->p_fw) {
++ for (i = 0, j = u->inputs; i < j; i++) {
++ if (u->p_fw[i])
++ fftwf_destroy_plan(u->p_fw[i]);
++ }
++ fftwf_free(u->p_fw);
++ }
+
+- if (u->input_buffer)
+- pa_xfree(u->input_buffer);
++ if (u->p_bw)
++ fftwf_destroy_plan(u->p_bw);
+
+- if (u->mapping_left)
+- pa_xfree(u->mapping_left);
+- if (u->mapping_right)
+- pa_xfree(u->mapping_right);
++ if (u->f_ir) {
++ for (i = 0, j = u->inputs * 2; i < j; i++) {
++ if (u->f_ir[i])
++ fftwf_free(u->f_ir[i]);
++ }
++ fftwf_free(u->f_ir);
++ }
++
++ if (u->f_out)
++ fftwf_free(u->f_out);
++
++ if (u->f_in)
++ fftwf_free(u->f_in);
++
++ if (u->revspace)
++ fftwf_free(u->revspace);
++
++ if (u->outspace[0])
++ fftwf_free(u->outspace[0]);
++ if (u->outspace[1])
++ fftwf_free(u->outspace[1]);
++
++ if (u->inspace) {
++ for (i = 0, j = u->inputs; i < j; i++) {
++ if (u->inspace[i])
++ fftwf_free(u->inspace[i]);
++ }
++ fftwf_free(u->inspace);
++ }
+
+ pa_xfree(u);
+ }
+diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
+index ed124cab4..6eb025489 100644
+--- a/src/modules/oss/module-oss.c
++++ b/src/modules/oss/module-oss.c
+@@ -980,10 +980,7 @@ static void thread_func(void *userdata) {
+
+ if (t < 0) {
+
+- if (errno == EINTR)
+- continue;
+-
+- else if (errno == EAGAIN) {
++ if (errno == EAGAIN) {
+ pa_log_debug("EAGAIN");
+
+ revents &= ~POLLOUT;
+@@ -1087,10 +1084,7 @@ static void thread_func(void *userdata) {
+ if (t < 0) {
+ pa_memblock_unref(memchunk.memblock);
+
+- if (errno == EINTR)
+- continue;
+-
+- else if (errno == EAGAIN) {
++ if (errno == EAGAIN) {
+ pa_log_debug("EAGAIN");
+
+ revents &= ~POLLIN;
+diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
+index 114f6d1e2..deba8d49a 100644
+--- a/src/modules/raop/raop-sink.c
++++ b/src/modules/raop/raop-sink.c
+@@ -826,7 +826,6 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
+ pa_sink_new_data_set_channel_map(&data, &map);
+
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server);
+- pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music");
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server);
+
+ if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+diff --git a/src/modules/x11/module-x11-bell.c b/src/modules/x11/module-x11-bell.c
+index eab1e6c3a..058a93316 100644
+--- a/src/modules/x11/module-x11-bell.c
++++ b/src/modules/x11/module-x11-bell.c
+@@ -93,6 +93,8 @@ static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
++ pa_log_debug("X11 client kill callback called");
++
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+diff --git a/src/modules/x11/module-x11-cork-request.c b/src/modules/x11/module-x11-cork-request.c
+index 6b1a86b9f..b9378f568 100644
+--- a/src/modules/x11/module-x11-cork-request.c
++++ b/src/modules/x11/module-x11-cork-request.c
+@@ -66,6 +66,8 @@ static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
++ pa_log_debug("X11 client kill callback called");
++
+ if (u->x11_client) {
+ pa_x11_client_free(u->x11_client);
+ u->x11_client = NULL;
+diff --git a/src/modules/x11/module-x11-publish.c b/src/modules/x11/module-x11-publish.c
+index 68adf1574..da619f83c 100644
+--- a/src/modules/x11/module-x11-publish.c
++++ b/src/modules/x11/module-x11-publish.c
+@@ -116,6 +116,8 @@ static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
++ pa_log_debug("X11 client kill callback called");
++
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+diff --git a/src/modules/x11/module-x11-xsmp.c b/src/modules/x11/module-x11-xsmp.c
+index 459da1302..1ff6cc770 100644
+--- a/src/modules/x11/module-x11-xsmp.c
++++ b/src/modules/x11/module-x11-xsmp.c
+@@ -55,21 +55,50 @@ struct userdata {
+ pa_module *module;
+ pa_client *client;
+ SmcConn connection;
+- pa_x11_wrapper *x11;
++
++ pa_x11_wrapper *x11_wrapper;
++ pa_x11_client *x11_client;
+ };
+
++static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
++ struct userdata *u = userdata;
++
++ pa_assert(w);
++ pa_assert(u);
++ pa_assert(u->x11_wrapper == w);
++
++ pa_log_debug("X11 client kill callback called");
++
++ if (u->connection) {
++ SmcCloseConnection(u->connection, 0, NULL);
++ u->connection = NULL;
++ }
++
++ if (u->x11_client) {
++ pa_x11_client_free(u->x11_client);
++ u->x11_client = NULL;
++ }
++
++ if (u->x11_wrapper) {
++ pa_x11_wrapper_unref(u->x11_wrapper);
++ u->x11_wrapper = NULL;
++ }
++
++ pa_module_unload_request(u->module, true);
++}
++
+ static void die_cb(SmcConn connection, SmPointer client_data) {
+ struct userdata *u = client_data;
+ pa_assert(u);
+
+ pa_log_debug("Got die message from XSMP.");
+
+- pa_x11_wrapper_kill(u->x11);
+-
+- pa_x11_wrapper_unref(u->x11);
+- u->x11 = NULL;
++ if (u->connection) {
++ SmcCloseConnection(u->connection, 0, NULL);
++ u->connection = NULL;
++ }
+
+- pa_module_unload_request(u->module, true);
++ pa_x11_wrapper_kill_deferred(u->x11_wrapper);
+ }
+
+ static void save_complete_cb(SmcConn connection, SmPointer client_data) {
+@@ -87,6 +116,7 @@ static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_fla
+ IceConn connection = userdata;
+
+ if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) {
++ pa_log_debug("IceProcessMessages: I/O error, closing ICE connection");
+ IceSetShutdownNegotiation(connection, False);
+ IceCloseConnection(connection);
+ }
+@@ -106,6 +136,17 @@ static void new_ice_connection(IceConn connection, IcePointer client_data, Bool
+ c->mainloop->io_free(*watch_data);
+ }
+
++static IceIOErrorHandler ice_installed_handler;
++
++/* We call any handler installed before (or after) module is loaded but
++ avoid calling the default libICE handler which does an exit() */
++
++static void ice_io_error_handler(IceConn iceConn) {
++ pa_log_warn("ICE I/O error handler called");
++ if (ice_installed_handler)
++ (*ice_installed_handler) (iceConn);
++}
++
+ int pa__init(pa_module*m) {
+
+ pa_modargs *ma = NULL;
+@@ -123,17 +164,27 @@ int pa__init(pa_module*m) {
+ if (ice_in_use) {
+ pa_log("module-x11-xsmp may not be loaded twice.");
+ return -1;
+- }
++ } else {
++ IceIOErrorHandler default_handler;
++
++ ice_installed_handler = IceSetIOErrorHandler (NULL);
++ default_handler = IceSetIOErrorHandler (ice_io_error_handler);
+
+- IceAddConnectionWatch(new_ice_connection, m->core);
+- ice_in_use = true;
++ if (ice_installed_handler == default_handler)
++ ice_installed_handler = NULL;
++
++ IceSetIOErrorHandler(ice_io_error_handler);
++
++ IceAddConnectionWatch(new_ice_connection, m->core);
++ ice_in_use = true;
++ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->client = NULL;
+ u->connection = NULL;
+- u->x11 = NULL;
++ u->x11_wrapper = NULL;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+@@ -147,9 +198,11 @@ int pa__init(pa_module*m) {
+ }
+ }
+
+- if (!(u->x11 = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
++ if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
++ u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u);
++
+ e = pa_modargs_get_value(ma, "session_manager", NULL);
+
+ if (!e && !getenv("SESSION_MANAGER")) {
+@@ -253,8 +306,11 @@ void pa__done(pa_module*m) {
+ if (u->client)
+ pa_client_free(u->client);
+
+- if (u->x11)
+- pa_x11_wrapper_unref(u->x11);
++ if (u->x11_client)
++ pa_x11_client_free(u->x11_client);
++
++ if (u->x11_wrapper)
++ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ pa_xfree(u);
+ }
+diff --git a/src/pulse/context.c b/src/pulse/context.c
+index 1d1bb9ee8..05b6633aa 100644
+--- a/src/pulse/context.c
++++ b/src/pulse/context.c
+@@ -128,6 +128,7 @@ static void reset_callbacks(pa_context *c) {
+ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, const pa_proplist *p) {
+ pa_context *c;
+ pa_mem_type_t type;
++ const char *force_disable_shm_str;
+
+ pa_assert(mainloop);
+
+@@ -173,6 +174,16 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *
+ c->conf = pa_client_conf_new();
+ pa_client_conf_load(c->conf, true, true);
+
++ force_disable_shm_str = pa_proplist_gets(c->proplist, PA_PROP_CONTEXT_FORCE_DISABLE_SHM);
++ if (force_disable_shm_str) {
++ int b = pa_parse_boolean(force_disable_shm_str);
++ if (b < 0) {
++ pa_log_warn("Ignored invalid value for '%s' property: %s", PA_PROP_CONTEXT_FORCE_DISABLE_SHM, force_disable_shm_str);
++ } else if (b) {
++ c->conf->disable_shm = true;
++ }
++ }
++
+ c->srb_template.readfd = -1;
+ c->srb_template.writefd = -1;
+
+diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c
+index 1ce3cd39e..77295def8 100644
+--- a/src/pulse/glib-mainloop.c
++++ b/src/pulse/glib-mainloop.c
+@@ -482,16 +482,15 @@ static gboolean prepare_func(GSource *source, gint *timeout) {
+ return TRUE;
+ } else if (g->n_enabled_time_events) {
+ pa_time_event *t;
+- GTimeVal now;
++ gint64 now;
+ struct timeval tvnow;
+ pa_usec_t usec;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+- g_get_current_time(&now);
+- tvnow.tv_sec = now.tv_sec;
+- tvnow.tv_usec = now.tv_usec;
++ now = g_get_real_time();
++ pa_timeval_store(&tvnow, now);
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) {
+ *timeout = 0;
+@@ -514,15 +513,14 @@ static gboolean check_func(GSource *source) {
+ return TRUE;
+ else if (g->n_enabled_time_events) {
+ pa_time_event *t;
+- GTimeVal now;
++ gint64 now;
+ struct timeval tvnow;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+- g_get_current_time(&now);
+- tvnow.tv_sec = now.tv_sec;
+- tvnow.tv_usec = now.tv_usec;
++ now = g_get_real_time();
++ pa_timeval_store(&tvnow, now);
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0)
+ return TRUE;
+@@ -558,16 +556,15 @@ static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer us
+ }
+
+ if (g->n_enabled_time_events) {
+- GTimeVal now;
++ gint64 now;
+ struct timeval tvnow;
+ pa_time_event *t;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+- g_get_current_time(&now);
+- tvnow.tv_sec = now.tv_sec;
+- tvnow.tv_usec = now.tv_usec;
++ now = g_get_real_time();
++ pa_timeval_store(&tvnow, now);
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) {
+
+diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
+index 3027f38ed..daae4cac4 100644
+--- a/src/pulse/introspect.c
++++ b/src/pulse/introspect.c
+@@ -2205,3 +2205,75 @@ pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in
+
+ return o;
+ }
++
++/** Object response string processing **/
++
++static void context_string_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++ pa_operation *o = userdata;
++ const char *response;
++ int success = 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;
++
++ success = 0;
++ response = "";
++ } else if (pa_tagstruct_gets(t, &response) < 0 ||
++ !pa_tagstruct_eof(t)) {
++ pa_context_fail(o->context, PA_ERR_PROTOCOL);
++ goto finish;
++ }
++
++ if (!response)
++ response = "";
++
++ if (o->callback) {
++ char *response_copy;
++ pa_context_string_cb_t cb;
++
++ response_copy = pa_xstrdup(response);
++
++ cb = (pa_context_string_cb_t) o->callback;
++ cb(o->context, success, response_copy, o->userdata);
++
++ pa_xfree(response_copy);
++ }
++
++finish:
++ pa_operation_done(o);
++ pa_operation_unref(o);
++}
++
++pa_operation* pa_context_send_message_to_object(pa_context *c, const char *object_path, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata) {
++ pa_operation *o;
++ pa_tagstruct *t;
++ uint32_t tag;
++
++ 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 >= 35, PA_ERR_NOTSUPPORTED);
++
++ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++ t = pa_tagstruct_command(c, PA_COMMAND_SEND_OBJECT_MESSAGE, &tag);
++
++ pa_tagstruct_puts(t, object_path);
++ pa_tagstruct_puts(t, message);
++ pa_tagstruct_puts(t, message_parameters);
++
++ pa_pstream_send_tagstruct(c->pstream, t);
++ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_string_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++ return o;
++}
+diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
+index c547bde09..2c3c4acf4 100644
+--- a/src/pulse/introspect.h
++++ b/src/pulse/introspect.h
+@@ -204,6 +204,12 @@
+ * Server modules can be remotely loaded and unloaded using
+ * pa_context_load_module() and pa_context_unload_module().
+ *
++ * \subsection message_subsec Messages
++ *
++ * Server objects like sinks, sink inputs or modules can register a message
++ * handler to communicate with clients. A message can be sent to a named
++ * message handler using pa_context_send_message_to_object().
++ *
+ * \subsection client_subsec Clients
+ *
+ * The only operation supported on clients is the possibility of kicking
+@@ -489,6 +495,17 @@ pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_s
+
+ /** @} */
+
++/** @{ \name Messages */
++
++/** Callback prototype for pa_context_send_message_to_object() \since 15.0 */
++typedef void (*pa_context_string_cb_t)(pa_context *c, int success, char *response, void *userdata);
++
++/** Send a message to an object that registered a message handler. For more information
++ * see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt. \since 15.0 */
++pa_operation* pa_context_send_message_to_object(pa_context *c, const char *recipient_name, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata);
++
++/** @} */
++
+ /** @{ \name Clients */
+
+ /** Stores information about clients. Please note that this structure
+diff --git a/src/pulse/meson.build b/src/pulse/meson.build
+index aaebff53e..8341bfcf0 100644
+--- a/src/pulse/meson.build
++++ b/src/pulse/meson.build
+@@ -19,6 +19,7 @@ libpulse_sources = [
+ 'mainloop-api.c',
+ 'mainloop-signal.c',
+ 'mainloop.c',
++ 'message-params.c',
+ 'operation.c',
+ 'proplist.c',
+ 'rtclock.c',
+@@ -50,6 +51,7 @@ libpulse_headers = [
+ 'mainloop-api.h',
+ 'mainloop-signal.h',
+ 'mainloop.h',
++ 'message-params.h',
+ 'operation.h',
+ 'proplist.h',
+ 'pulseaudio.h',
+@@ -81,7 +83,7 @@ libpulse = shared_library('pulse',
+ link_args : [nodelete_link_args, versioning_link_args],
+ install : true,
+ install_rpath : privlibdir,
+- dependencies : [libm_dep, thread_dep, libpulsecommon_dep, dbus_dep, dl_dep, iconv_dep, libintl_dep],
++ dependencies : [libm_dep, thread_dep, libpulsecommon_dep, dbus_dep, dl_dep, iconv_dep, libintl_dep, platform_dep, platform_socket_dep],
+ implicit_include_directories : false)
+
+ libpulse_dep = declare_dependency(link_with: libpulse)
+diff --git a/src/pulse/message-params.c b/src/pulse/message-params.c
+new file mode 100644
+index 000000000..99402ebc2
+--- /dev/null
++++ b/src/pulse/message-params.c
+@@ -0,0 +1,641 @@
++/***
++ 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.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
++ Lesser General Public License for more details.
++
++ You should have received a copy of the GNU Lesser General Public
++ License along with PulseAudio; if not, see .
++***/
++
++#ifdef HAVE_CONFIG_H
++#include
++#endif
++
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++
++#include
++#include
++#include
++
++#include "message-params.h"
++
++/* Message parameter structure, a wrapper for pa_strbuf */
++struct pa_message_params {
++ pa_strbuf *buffer;
++};
++
++/* Helper functions */
++
++/* Count number of top level elements in parameter list */
++static int count_elements(const char *c) {
++ const char *s;
++ uint32_t element_count;
++ bool found_element, found_backslash;
++ int open_braces;
++
++ if (!c || *c == 0)
++ return PA_MESSAGE_PARAMS_LIST_END;
++
++ element_count = 0;
++ open_braces = 0;
++ found_element = false;
++ found_backslash = false;
++ s = c;
++
++ /* Count elements in list */
++ while (*s != 0) {
++
++ /* Skip escaped curly braces. */
++ if (*s == '\\' && !found_backslash) {
++ found_backslash = true;
++ s++;
++ continue;
++ }
++
++ if (*s == '{' && !found_backslash) {
++ found_element = true;
++ open_braces++;
++ }
++ if (*s == '}' && !found_backslash)
++ open_braces--;
++
++ /* unexpected closing brace, parse error */
++ if (open_braces < 0)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ if (open_braces == 0 && found_element) {
++ element_count++;
++ found_element = false;
++ }
++
++ found_backslash = false;
++ s++;
++ }
++
++ /* missing closing brace, parse error */
++ if (open_braces > 0)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ return element_count;
++}
++
++/* Split the specified string into elements. An element is defined as
++ * a sub-string between curly braces. The function is needed to parse
++ * the parameters of messages. Each time it is called it returns the
++ * position of the current element in result and the state pointer is
++ * advanced to the next list element. On return, the parameter
++ * *is_unpacked indicates if the string is plain text or contains a
++ * sub-list. is_unpacked may be NULL.
++ *
++ * The variable state points to, should be initialized to NULL before
++ * the first call. The function returns 1 on success, 0 if end of string
++ * is encountered and -1 on parse error.
++ *
++ * result is set to NULL on end of string or parse error. */
++static int split_list(char *c, char **result, bool *is_unpacked, void **state) {
++ char *current = *state ? *state : c;
++ uint32_t open_braces;
++ bool found_backslash = false;
++
++ pa_assert(result);
++
++ *result = NULL;
++
++ /* Empty or no string */
++ if (!current || *current == 0)
++ return PA_MESSAGE_PARAMS_LIST_END;
++
++ /* Find opening brace */
++ while (*current != 0) {
++
++ /* Skip escaped curly braces. */
++ if (*current == '\\' && !found_backslash) {
++ found_backslash = true;
++ current++;
++ continue;
++ }
++
++ if (*current == '{' && !found_backslash)
++ break;
++
++ /* unexpected closing brace, parse error */
++ if (*current == '}' && !found_backslash)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ found_backslash = false;
++ current++;
++ }
++
++ /* No opening brace found, end of string */
++ if (*current == 0)
++ return PA_MESSAGE_PARAMS_LIST_END;
++
++ if (is_unpacked)
++ *is_unpacked = true;
++ *result = current + 1;
++ found_backslash = false;
++ open_braces = 1;
++
++ while (open_braces != 0 && *current != 0) {
++ current++;
++
++ /* Skip escaped curly braces. */
++ if (*current == '\\' && !found_backslash) {
++ found_backslash = true;
++ continue;
++ }
++
++ if (*current == '{' && !found_backslash) {
++ open_braces++;
++ if (is_unpacked)
++ *is_unpacked = false;
++ }
++ if (*current == '}' && !found_backslash)
++ open_braces--;
++
++ found_backslash = false;
++ }
++
++ /* Parse error, closing brace missing */
++ if (open_braces != 0) {
++ *result = NULL;
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ }
++
++ /* Replace } with 0 */
++ *current = 0;
++
++ *state = current + 1;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Read functions */
++
++/* Read a string from the parameter list. The state pointer is
++ * advanced to the next element of the list. Returns a pointer
++ * to a sub-string within c. Escape characters will be removed
++ * from the string. The result must not be freed. */
++int pa_message_params_read_string(char *c, const char **result, void **state) {
++ char *start_pos;
++ char *value = NULL;
++ int r;
++ bool is_unpacked = true;
++
++ pa_assert(result);
++
++ if ((r = split_list(c, &start_pos, &is_unpacked, state)) == PA_MESSAGE_PARAMS_OK)
++ value = start_pos;
++
++ /* Check if we got a plain string not containing further lists */
++ if (!is_unpacked) {
++ /* Parse error */
++ r = PA_MESSAGE_PARAMS_PARSE_ERROR;
++ value = NULL;
++ }
++
++ if (value)
++ *result = pa_unescape(value);
++
++ return r;
++}
++
++/* A wrapper for split_list() to distinguish between reading pure
++ * string data and raw data which may contain further lists. */
++int pa_message_params_read_raw(char *c, char **result, void **state) {
++ return split_list(c, result, NULL, state);
++}
++
++/* Read a double from the parameter list. The state pointer is
++ * advanced to the next element of the list. */
++int pa_message_params_read_double(char *c, double *result, void **state) {
++ char *start_pos, *end_pos, *s;
++ int err;
++ struct lconv *locale;
++ double value;
++ bool is_unpacked = true;
++
++ pa_assert(result);
++
++ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Empty element */
++ if (!*start_pos)
++ return PA_MESSAGE_PARAMS_IS_NULL;
++
++ /* Check if we got a plain string not containing further lists */
++ if (!is_unpacked)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ /* Get decimal separator for current locale */
++ locale = localeconv();
++
++ /* Replace decimal point with the correct character for the
++ * current locale. This assumes that no thousand separator
++ * is used. */
++ for (s = start_pos; *s; s++) {
++ if (*s == '.' || *s == ',')
++ *s = *locale->decimal_point;
++ }
++
++ /* Convert to double */
++ errno = 0;
++ value = strtod(start_pos, &end_pos);
++
++ /* Conversion error or string contains invalid characters. If the
++ * whole string was used for conversion, end_pos should point to
++ * the end of the string. */
++ if (errno != 0 || *end_pos != 0 || end_pos == start_pos)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ *result = value;
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Read an integer from the parameter list. The state pointer is
++ * advanced to the next element of the list. */
++int pa_message_params_read_int64(char *c, int64_t *result, void **state) {
++ char *start_pos;
++ int err;
++ int64_t value;
++ bool is_unpacked = true;
++
++ pa_assert(result);
++
++ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Empty element */
++ if (!*start_pos)
++ return PA_MESSAGE_PARAMS_IS_NULL;
++
++ /* Check if we got a plain string not containing further lists */
++ if (!is_unpacked)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ /* Convert to int64 */
++ if (pa_atoi64(start_pos, &value) < 0)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ *result = value;
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Read an unsigned integer from the parameter list. The state pointer is
++ * advanced to the next element of the list. */
++int pa_message_params_read_uint64(char *c, uint64_t *result, void **state) {
++ char *start_pos;
++ int err;
++ uint64_t value;
++ bool is_unpacked = true;
++
++ pa_assert(result);
++
++ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Empty element */
++ if (!*start_pos)
++ return PA_MESSAGE_PARAMS_IS_NULL;
++
++ /* Check if we got a plain string not containing further lists */
++ if (!is_unpacked)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ /* Convert to int64 */
++ if (pa_atou64(start_pos, &value) < 0)
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++
++ *result = value;
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Read a boolean from the parameter list. The state pointer is
++ * advanced to the next element of the list. */
++int pa_message_params_read_bool(char *c, bool *result, void **state) {
++ int err;
++ uint64_t value;
++
++ pa_assert(result);
++
++ if ((err = pa_message_params_read_uint64(c, &value, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ *result = false;
++ if (value)
++ *result = true;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Converts a parameter list to a string array. */
++int pa_message_params_read_string_array(char *c, const char ***results, int *length, void **state) {
++ void *state1 = NULL;
++ int element_count, i;
++ int err;
++ const char **values;
++ char *start_pos;
++
++ pa_assert(results);
++ pa_assert(length);
++
++ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Count elements, return if no element was found or parse error. */
++ element_count = count_elements(start_pos);
++ if (element_count < 0) {
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ } else if (element_count == 0) {
++ *length = 0;
++ return PA_MESSAGE_PARAMS_OK;
++ }
++
++ /* Allocate array */
++ values = pa_xmalloc0(element_count * sizeof(char *));
++
++ for (i = 0; (err = pa_message_params_read_string(start_pos, &(values[i]), &state1)) > 0; i++)
++ ;
++
++ if (err < 0) {
++ pa_xfree(values);
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ }
++
++ *results = values;
++ *length = element_count;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Converts a parameter list to a double array. */
++int pa_message_params_read_double_array(char *c, double **results, int *length, void **state) {
++ double *values;
++ void *state1 = NULL;
++ int element_count, i;
++ int err;
++ char *start_pos;
++
++ pa_assert(results);
++ pa_assert(length);
++
++ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Count elements, return if no element was found or parse error. */
++ element_count = count_elements(start_pos);
++ if (element_count < 0) {
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ } else if (element_count == 0) {
++ *length = 0;
++ return PA_MESSAGE_PARAMS_OK;
++ }
++
++ /* Allocate array */
++ values = pa_xmalloc0(element_count * sizeof(double));
++
++ for (i = 0; (err = pa_message_params_read_double(start_pos, &(values[i]), &state1)) > 0; i++)
++ ;
++
++ if (err < 0) {
++ pa_xfree(values);
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ }
++
++ *results = values;
++ *length = element_count;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Converts a parameter list to an int64 array. */
++int pa_message_params_read_int64_array(char *c, int64_t **results, int *length, void **state) {
++ int64_t *values;
++ void *state1 = NULL;
++ int element_count, i;
++ int err;
++ char *start_pos;
++
++ pa_assert(results);
++ pa_assert(length);
++
++ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Count elements, return if no element was found or parse error. */
++ element_count = count_elements(start_pos);
++ if (element_count < 0) {
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ } else if (element_count == 0) {
++ *length = 0;
++ return PA_MESSAGE_PARAMS_OK;
++ }
++
++ /* Allocate array */
++ values = pa_xmalloc0(element_count * sizeof(int64_t));
++
++ for (i = 0; (err = pa_message_params_read_int64(start_pos, &(values[i]), &state1)) > 0; i++)
++ ;
++
++ if (err < 0) {
++ pa_xfree(values);
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ }
++
++ *results = values;
++ *length = element_count;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Converts a parameter list to an uint64 array. */
++int pa_message_params_read_uint64_array(char *c, uint64_t **results, int *length, void **state) {
++ uint64_t *values;
++ void *state1 = NULL;
++ int element_count, i;
++ int err;
++ char *start_pos;
++
++ pa_assert(results);
++ pa_assert(length);
++
++ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
++ return err;
++
++ /* Count elements, return if no element was found or parse error. */
++ element_count = count_elements(start_pos);
++ if (element_count < 0) {
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ } else if (element_count == 0) {
++ *length = 0;
++ return PA_MESSAGE_PARAMS_OK;
++ }
++
++ /* Allocate array */
++ values = pa_xmalloc0(element_count * sizeof(uint64_t));
++
++ for (i = 0; (err = pa_message_params_read_uint64(start_pos, &(values[i]), &state1)) > 0; i++)
++ ;
++
++ if (err < 0) {
++ pa_xfree(values);
++ return PA_MESSAGE_PARAMS_PARSE_ERROR;
++ }
++
++ *results = values;
++ *length = element_count;
++
++ return PA_MESSAGE_PARAMS_OK;
++}
++
++/* Write functions. The functions are wrapper functions around pa_strbuf,
++ * so that the client does not need to use pa_strbuf directly. */
++
++/* Creates a new pa_message_param structure */
++pa_message_params *pa_message_params_new(void) {
++ pa_message_params *params;
++
++ params = pa_xnew(pa_message_params, 1);
++ params->buffer = pa_strbuf_new();
++
++ return params;
++}
++
++/* Frees a pa_message_params structure */
++void pa_message_params_free(pa_message_params *params) {
++ pa_assert(params);
++
++ pa_strbuf_free(params->buffer);
++ pa_xfree(params);
++}
++
++/* Converts a pa_message_param structure to string and frees the structure.
++ * The returned string needs to be freed with pa_xree(). */
++char *pa_message_params_to_string_free(pa_message_params *params) {
++ char *result;
++
++ pa_assert(params);
++
++ result = pa_strbuf_to_string_free(params->buffer);
++
++ pa_xfree(params);
++ return result;
++}
++
++/* Writes an opening curly brace */
++void pa_message_params_begin_list(pa_message_params *params) {
++
++ pa_assert(params);
++
++ pa_strbuf_putc(params->buffer, '{');
++}
++
++/* Writes a closing curly brace */
++void pa_message_params_end_list(pa_message_params *params) {
++
++ pa_assert(params);
++
++ pa_strbuf_putc(params->buffer, '}');
++}
++
++/* Writes a string to a message_params structure, adding curly braces
++ * around the string and escaping curly braces within the string. */
++void pa_message_params_write_string(pa_message_params *params, const char *value) {
++ char *output;
++
++ pa_assert(params);
++
++ /* Null value is written as empty element */
++ if (!value)
++ value = "";
++
++ output = pa_escape(value, "{}");
++ pa_strbuf_printf(params->buffer, "{%s}", output);
++
++ pa_xfree(output);
++}
++
++/* Writes a raw string to a message_params structure, adding curly braces
++ * around the string if add_braces is true. This function can be used to
++ * write parts of a string or whole parameter lists that have been prepared
++ * elsewhere (for example an array). */
++void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces) {
++ pa_assert(params);
++
++ /* Null value is written as empty element if add_braces is true.
++ * Otherwise nothing is written. */
++ if (!value)
++ value = "";
++
++ if (add_braces)
++ pa_strbuf_printf(params->buffer, "{%s}", value);
++ else
++ pa_strbuf_puts(params->buffer, value);
++}
++
++/* Writes a double to a message_params structure, adding curly braces.
++ * precision gives the number of significant digits, not digits after
++ * the decimal point. */
++void pa_message_params_write_double(pa_message_params *params, double value, int precision) {
++ char *buf, *s;
++
++ pa_assert(params);
++
++ /* We do not care about locale because we do not know which locale is
++ * used on the server side. If the decimal separator is a comma, we
++ * replace it with a dot to achieve consistent output on all locales. */
++ buf = pa_sprintf_malloc("{%.*g}", precision, value);
++ for (s = buf; *s; s++) {
++ if (*s == ',') {
++ *s = '.';
++ break;
++ }
++ }
++
++ pa_strbuf_puts(params->buffer, buf);
++
++ pa_xfree(buf);
++}
++
++/* Writes an integer to a message_param structure, adding curly braces. */
++void pa_message_params_write_int64(pa_message_params *params, int64_t value) {
++
++ pa_assert(params);
++
++ pa_strbuf_printf(params->buffer, "{%lli}", (long long)value);
++}
++
++/* Writes an unsigned integer to a message_params structure, adding curly braces. */
++void pa_message_params_write_uint64(pa_message_params *params, uint64_t value) {
++
++ pa_assert(params);
++
++ pa_strbuf_printf(params->buffer, "{%llu}", (unsigned long long)value);
++}
++
++/* Writes a boolean to a message_params structure, adding curly braces. */
++void pa_message_params_write_bool(pa_message_params *params, bool value) {
++
++ pa_assert(params);
++
++ if (value)
++ pa_strbuf_puts(params->buffer, "{1}");
++ else
++ pa_strbuf_puts(params->buffer, "{0}");
++}
+diff --git a/src/pulse/message-params.h b/src/pulse/message-params.h
+new file mode 100644
+index 000000000..3e53c1e84
+--- /dev/null
++++ b/src/pulse/message-params.h
+@@ -0,0 +1,157 @@
++#ifndef foomessagehelperhfoo
++#define foomessagehelperhfoo
++
++/***
++ 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.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
++ Lesser General Public License for more details.
++
++ You should have received a copy of the GNU Lesser General Public
++ License along with PulseAudio; if not, see .
++***/
++
++#include
++#include
++#include
++
++#include
++#include
++
++/** \file
++ * Utility functions for reading and writing message parameters.
++ * All read functions return a value from pa_message_params_error_code
++ * and the read value in result (or *result for string functions).
++ * The string read functions read_string() and read_raw() return a pointer
++ * to a sub-string within the parameter list in *result, therefore the
++ * string in *result must not be freed and is only valid within the
++ * message handler callback function. If the string is needed outside
++ * the callback, it must be copied using pa_xstrdup().
++ * When a read function is called, the state pointer is advanced to the
++ * next list element. The variable state points to should be initialized
++ * to NULL before the first call.
++ * All read functions except read_raw() preserve a default value passed
++ * in result if the call fails. For the array functions, results must be
++ * initialized prior to the call either to NULL or to an array with default
++ * values. If the function succeeds, the default array will be freed and
++ * the number of elements in the result array is returned.\n\n
++ * Write functions operate on a pa_message_params structure which is a
++ * wrapper for pa_strbuf. A parameter list or sub-list is started by a
++ * call to begin_list() and ended by a call to end_list().
++ * A pa_message_params structure must be converted to a string using
++ * pa_message_params_to_string_free() before it can be passed to a
++ * message handler. */
++
++PA_C_DECL_BEGIN
++
++/** Structure which holds a parameter list. Wrapper for pa_strbuf \since 15.0 */
++typedef struct pa_message_params pa_message_params;
++
++/** Read function return values \since 15.0 */
++enum pa_message_params_error_code {
++ /** No value (empty element) found for numeric or boolean value */
++ PA_MESSAGE_PARAMS_IS_NULL = -2,
++ /** Error encountered while parsing a value */
++ PA_MESSAGE_PARAMS_PARSE_ERROR = -1,
++ /** End of parameter list reached */
++ PA_MESSAGE_PARAMS_LIST_END = 0,
++ /** Parsing successful */
++ PA_MESSAGE_PARAMS_OK = 1,
++};
++
++/** @{ \name Read functions */
++
++/** Read a boolean from parameter list in c. \since 15.0 */
++int pa_message_params_read_bool(char *c, bool *result, void **state);
++
++/** Read a double from parameter list in c. \since 15.0 */
++int pa_message_params_read_double(char *c, double *result, void **state);
++
++/** Converts a parameter list to a double array. Empty elements in the parameter
++ * list are treated as error. Returns allocated array in *results and array size in *length.
++ * The returned array must be freed with pa_xfree(). \since 15.0 */
++int pa_message_params_read_double_array(char *c, double **results, int *length, void **state);
++
++/** Read an integer from parameter list in c. \since 15.0 */
++int pa_message_params_read_int64(char *c, int64_t *result, void **state);
++
++/** Converts a parameter list to an int64 array. Empty elements in the parameter
++ * list are treated as error. Returns allocated array in *results and array size in *length.
++ * The returned array must be freed with pa_xfree(). \since 15.0 */
++int pa_message_params_read_int64_array(char *c, int64_t **results, int *length, void **state);
++
++/** Read raw data from parameter list in c. Used to split a message parameter
++ * string into list elements. The string returned in *result must not be freed. \since 15.0 */
++int pa_message_params_read_raw(char *c, char **result, void **state);
++
++/** Read a string from a parameter list in c. Escaped curly braces and backslashes
++ * will be unescaped. \since 15.0 */
++int pa_message_params_read_string(char *c, const char **result, void **state);
++
++/** Convert a parameter list to a string array. Escaping is removed from
++ * the strings. Returns allocated array of pointers to sub-strings within c in
++ * *results and stores array size in *length. The returned array must be
++ * freed with pa_xfree(), but not the strings within the array. \since 15.0 */
++int pa_message_params_read_string_array(char *c, const char ***results, int *length, void **state);
++
++/** Read an unsigned integer from parameter list in c. \since 15.0 */
++int pa_message_params_read_uint64(char *c, uint64_t *result, void **state);
++
++/** Converts a parameter list to an uint64 array. Empty elements in the parameter
++ * list are treated as error. Returns allocated array in *results and array size in *length.
++ * The returned array must be freed with pa_xfree(). \since 15.0 */
++int pa_message_params_read_uint64_array(char *c, uint64_t **results, int *length, void **state);
++
++/** @} */
++
++/** @{ \name Write functions */
++
++/** Create a new pa_message_params structure. \since 15.0 */
++pa_message_params *pa_message_params_new(void);
++
++/** Free a pa_message_params structure. \since 15.0 */
++void pa_message_params_free(pa_message_params *params);
++
++/** Convert pa_message_params to string, free pa_message_params structure. \since 15.0 */
++char *pa_message_params_to_string_free(pa_message_params *params);
++
++/** Start a list by writing an opening brace. \since 15.0 */
++void pa_message_params_begin_list(pa_message_params *params);
++
++/** End a list by writing a closing brace. \since 15.0 */
++void pa_message_params_end_list(pa_message_params *params);
++
++/** Append a boolean to parameter list. \since 15.0 */
++void pa_message_params_write_bool(pa_message_params *params, bool value);
++
++/** Append a double to parameter list. Precision gives the number of
++ * significant digits. The decimal separator will always be written as
++ * dot, regardless which locale is used. \since 15.0 */
++void pa_message_params_write_double(pa_message_params *params, double value, int precision);
++
++/** Append an integer to parameter list. \since 15.0 */
++void pa_message_params_write_int64(pa_message_params *params, int64_t value);
++
++/** Append string to parameter list. Curly braces and backslashes will be escaped. \since 15.0 */
++void pa_message_params_write_string(pa_message_params *params, const char *value);
++
++/** Append raw string to parameter list. Used to write incomplete strings
++ * or complete parameter lists (for example arrays). Adds curly braces around
++ * the string if add_braces is true. \since 15.0 */
++void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces);
++
++/** Append an unsigned integer to parameter list. \since 15.0 */
++void pa_message_params_write_uint64(pa_message_params *params, uint64_t value);
++
++/** @} */
++
++PA_C_DECL_END
++
++#endif
+diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
+index e50518b37..5da01664b 100644
+--- a/src/pulse/proplist.h
++++ b/src/pulse/proplist.h
+@@ -267,6 +267,9 @@ PA_C_DECL_BEGIN
+ /** For PCM formats: the channel map of the stream as returned by pa_channel_map_snprint() \since 1.0 */
+ #define PA_PROP_FORMAT_CHANNEL_MAP "format.channel_map"
+
++/** For context: whether to forcefully disable data transfer via POSIX or memfd shared memory. This property overrides any other client configuration which would otherwise enable SHM communication channels. \since 15.0 */
++#define PA_PROP_CONTEXT_FORCE_DISABLE_SHM "context.force.disable.shm"
++
+ /** A property list object. Basically a dictionary with ASCII strings
+ * as keys and arbitrary data as values. \since 0.9.11 */
+ typedef struct pa_proplist pa_proplist;
+diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
+index 5205349bd..c5a21ed93 100644
+--- a/src/pulsecore/cli-command.c
++++ b/src/pulsecore/cli-command.c
+@@ -53,6 +53,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -135,6 +136,7 @@ static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,
+ static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+ static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+ static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
++static int pa_cli_command_send_message_to_object(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+
+ /* A method table for all available commands */
+
+@@ -191,6 +193,7 @@ static const struct command commands[] = {
+ { "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2},
+ { "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2},
+ { "set-log-backtrace", pa_cli_command_log_backtrace, "Show backtrace in log messages (args: frames)", 2},
++ { "send-message", pa_cli_command_send_message_to_object, "Send a message to an object (args: recipient, message, message_parameters)", 4},
+ { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3},
+ { "dump", pa_cli_command_dump, "Dump daemon configuration", 1},
+ { "dump-volumes", pa_cli_command_dump_volumes, "Debug: Show the state of all volumes", 1 },
+@@ -1784,6 +1787,47 @@ static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
+ return 0;
+ }
+
++static int pa_cli_command_send_message_to_object(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
++ const char *object_path, *message, *message_parameters;
++ char *response = NULL;
++ int ret;
++
++ pa_core_assert_ref(c);
++ pa_assert(t);
++ pa_assert(buf);
++ pa_assert(fail);
++
++
++ if (!(object_path = pa_tokenizer_get(t, 1))) {
++ pa_strbuf_puts(buf, "You need to specify an object path as recipient for the message.\n");
++ return -1;
++ }
++
++ if (!(message = pa_tokenizer_get(t, 2))) {
++ pa_strbuf_puts(buf, "You need to specify a message name.\n");
++ return -1;
++ }
++
++ /* parameters may be NULL */
++ message_parameters = pa_tokenizer_get(t, 3);
++
++ ret = pa_message_handler_send_message(c, object_path, message, message_parameters, &response);
++
++ if (ret < 0) {
++ pa_strbuf_printf(buf, "Send message failed: %s\n", pa_strerror(ret));
++ ret = -1;
++
++ } else {
++ if (response)
++ pa_strbuf_puts(buf, response);
++ pa_strbuf_puts(buf, "\n");
++ ret = 0;
++ }
++
++ pa_xfree(response);
++ return ret;
++}
++
+ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_module *m;
+ pa_sink *sink;
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 601b1d1df..1c9746dbf 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -407,6 +407,8 @@ ssize_t pa_read(int fd, void *buf, size_t count, int *type) {
+
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
++ if (errno == WSAEWOULDBLOCK)
++ errno = EAGAIN;
+ return r;
+ }
+
+@@ -448,6 +450,8 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) {
+ #ifdef OS_IS_WIN32
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
++ if (errno == WSAEWOULDBLOCK)
++ errno = EAGAIN;
+ return r;
+ }
+ #else
+@@ -1568,6 +1572,70 @@ int pa_get_config_home_dir(char **_r) {
+ return 0;
+ }
+
++int pa_get_data_home_dir(char **_r) {
++ const char *e;
++ char *home_dir;
++
++ pa_assert(_r);
++
++ e = getenv("XDG_DATA_HOME");
++ if (e && *e) {
++ if (pa_is_path_absolute(e)) {
++ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "pulseaudio", e);
++ return 0;
++ }
++ else
++ pa_log_warn("Ignored non-absolute XDG_DATA_HOME value '%s'", e);
++ }
++
++ home_dir = pa_get_home_dir_malloc();
++ if (!home_dir)
++ return -PA_ERR_NOENTITY;
++
++ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP ".local" PA_PATH_SEP "share" PA_PATH_SEP "pulseaudio", home_dir);
++ pa_xfree(home_dir);
++ return 0;
++}
++
++int pa_get_data_dirs(pa_dynarray **_r) {
++ const char *e;
++ const char *def = "/usr/local/share/:/usr/share/";
++ const char *p;
++ const char *split_state = NULL;
++ char *n;
++ pa_dynarray *paths;
++
++ pa_assert(_r);
++
++ e = getenv("XDG_DATA_DIRS");
++ p = e && *e ? e : def;
++
++ paths = pa_dynarray_new((pa_free_cb_t) pa_xfree);
++
++ while ((n = pa_split(p, ":", &split_state))) {
++ char *path;
++
++ if (!pa_is_path_absolute(n)) {
++ pa_log_warn("Ignored non-absolute path '%s' in XDG_DATA_DIRS", n);
++ pa_xfree(n);
++ continue;
++ }
++
++ path = pa_sprintf_malloc("%s" PA_PATH_SEP "pulseaudio", n);
++ pa_xfree(n);
++ pa_dynarray_append(paths, path);
++ }
++
++ if (pa_dynarray_size(paths) == 0) {
++ pa_log_warn("XDG_DATA_DIRS contains no valid paths");
++ pa_dynarray_free(paths);
++ return -PA_ERR_INVALID;
++ }
++
++ *_r = paths;
++ return 0;
++}
++
+ int pa_append_to_config_home_dir(const char *path, char **_r) {
+ int r;
+ char *config_home_dir;
+@@ -2189,7 +2257,7 @@ int pa_atoi(const char *s, int32_t *ret_i) {
+ if (pa_atol(s, &l) < 0)
+ return -1;
+
+- if ((int32_t) l != l) {
++ if (l < INT32_MIN || l > INT32_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+@@ -2199,6 +2267,90 @@ int pa_atoi(const char *s, int32_t *ret_i) {
+ return 0;
+ }
+
++enum numtype {
++ NUMTYPE_UINT,
++ NUMTYPE_INT,
++ NUMTYPE_DOUBLE,
++};
++
++/* A helper function for pa_atou() and friends. This does some common checks,
++ * because our number parsing is more strict than the strtoX functions.
++ *
++ * Leading zeros are stripped from integers so that they don't get parsed as
++ * octal (but "0x" is preserved for hexadecimal numbers). For NUMTYPE_INT the
++ * zero stripping may involve allocating a new string, in which case it's
++ * stored in tmp. Otherwise tmp is set to NULL. The caller needs to free tmp
++ * after they're done with ret. When parsing other types than NUMTYPE_INT the
++ * caller can pass NULL as tmp.
++ *
++ * The final string to parse is returned in ret. ret will point either inside
++ * s or to tmp. */
++static int prepare_number_string(const char *s, enum numtype type, char **tmp, const char **ret) {
++ const char *original = s;
++ bool negative = false;
++
++ pa_assert(s);
++ pa_assert(type != NUMTYPE_INT || tmp);
++ pa_assert(ret);
++
++ if (tmp)
++ *tmp = NULL;
++
++ /* The strtoX functions accept leading spaces, we don't. */
++ if (isspace((unsigned char) s[0]))
++ return -1;
++
++ /* The strtoX functions accept a plus sign, we don't. */
++ if (s[0] == '+')
++ return -1;
++
++ /* The strtoul and strtoull functions allow a minus sign even though they
++ * parse an unsigned number. In case of a minus sign the original negative
++ * number gets negated. We don't want that kind of behviour. */
++ if (type == NUMTYPE_UINT && s[0] == '-')
++ return -1;
++
++ /* The strtoX functions interpret the number as octal if it starts with
++ * a zero. We prefer to use base 10, so we strip all leading zeros (if the
++ * string starts with "0x", strtoul() interprets it as hexadecimal, which
++ * is fine, because it's unambiguous unlike octal).
++ *
++ * While stripping the leading zeros, we have to remember to also handle
++ * the case where the number is negative, which makes the zero skipping
++ * code somewhat complex. */
++
++ /* Doubles don't need zero stripping, we can finish now. */
++ if (type == NUMTYPE_DOUBLE)
++ goto finish;
++
++ if (s[0] == '-') {
++ negative = true;
++ s++; /* Skip the minus sign. */
++ }
++
++ /* Don't skip zeros if the string starts with "0x". */
++ if (s[0] == '0' && s[1] != 'x') {
++ while (s[0] == '0' && s[1])
++ s++; /* Skip zeros. */
++ }
++
++ if (negative) {
++ s--; /* Go back one step, we need the minus sign back. */
++
++ /* If s != original, then we have skipped some zeros and we need to replace
++ * the last skipped zero with a minus sign. */
++ if (s != original) {
++ *tmp = pa_xstrdup(s);
++ *tmp[0] = '-';
++ s = *tmp;
++ }
++ }
++
++finish:
++ *ret = s;
++ return 0;
++}
++
+ /* Convert the string s to an unsigned integer in *ret_u */
+ int pa_atou(const char *s, uint32_t *ret_u) {
+ char *x = NULL;
+@@ -2207,23 +2359,47 @@ int pa_atou(const char *s, uint32_t *ret_u) {
+ pa_assert(s);
+ pa_assert(ret_u);
+
+- /* strtoul() ignores leading spaces. We don't. */
+- if (isspace((unsigned char)*s)) {
++ if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+- /* strtoul() accepts strings that start with a minus sign. In that case the
+- * original negative number gets negated, and strtoul() returns the negated
+- * result. We don't want that kind of behaviour. strtoul() also allows a
+- * leading plus sign, which is also a thing that we don't want. */
+- if (*s == '-' || *s == '+') {
++ errno = 0;
++ l = strtoul(s, &x, 0);
++
++ /* If x doesn't point to the end of s, there was some trailing garbage in
++ * the string. If x points to s, no conversion was done (empty string). */
++ if (!x || *x || x == s || errno) {
++ if (!errno)
++ errno = EINVAL;
++ return -1;
++ }
++
++ if (l > UINT32_MAX) {
++ errno = ERANGE;
++ return -1;
++ }
++
++ *ret_u = (uint32_t) l;
++
++ return 0;
++}
++
++/* Convert the string s to an unsigned 64 bit integer in *ret_u */
++int pa_atou64(const char *s, uint64_t *ret_u) {
++ char *x = NULL;
++ unsigned long long l;
++
++ pa_assert(s);
++ pa_assert(ret_u);
++
++ if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+- l = strtoul(s, &x, 0);
++ l = strtoull(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (empty string). */
+@@ -2233,39 +2409,66 @@ int pa_atou(const char *s, uint32_t *ret_u) {
+ return -1;
+ }
+
+- if ((uint32_t) l != l) {
++ if (l > UINT64_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+- *ret_u = (uint32_t) l;
++ *ret_u = (uint64_t) l;
+
+ return 0;
+ }
+
+ /* Convert the string s to a signed long integer in *ret_l. */
+ int pa_atol(const char *s, long *ret_l) {
++ char *tmp;
+ char *x = NULL;
+ long l;
+
+ pa_assert(s);
+ pa_assert(ret_l);
+
+- /* strtol() ignores leading spaces. We don't. */
+- if (isspace((unsigned char)*s)) {
++ if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+- /* strtol() accepts leading plus signs, but that's ugly, so we don't allow
+- * that. */
+- if (*s == '+') {
++ errno = 0;
++ l = strtol(s, &x, 0);
++
++ /* If x doesn't point to the end of s, there was some trailing garbage in
++ * the string. If x points to s, no conversion was done (at least an empty
++ * string can trigger this). */
++ if (!x || *x || x == s || errno) {
++ if (!errno)
++ errno = EINVAL;
++ pa_xfree(tmp);
++ return -1;
++ }
++
++ pa_xfree(tmp);
++
++ *ret_l = l;
++
++ return 0;
++}
++
++/* Convert the string s to a signed 64 bit integer in *ret_l. */
++int pa_atoi64(const char *s, int64_t *ret_l) {
++ char *tmp;
++ char *x = NULL;
++ long long l;
++
++ pa_assert(s);
++ pa_assert(ret_l);
++
++ if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+- l = strtol(s, &x, 0);
++ l = strtoll(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (at least an empty
+@@ -2273,11 +2476,19 @@ int pa_atol(const char *s, long *ret_l) {
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
++ pa_xfree(tmp);
+ return -1;
+ }
+
++ pa_xfree(tmp);
++
+ *ret_l = l;
+
++ if (l < INT64_MIN || l > INT64_MAX) {
++ errno = ERANGE;
++ return -1;
++ }
++
+ return 0;
+ }
+
+@@ -2296,15 +2507,7 @@ int pa_atod(const char *s, double *ret_d) {
+ pa_assert(s);
+ pa_assert(ret_d);
+
+- /* strtod() ignores leading spaces. We don't. */
+- if (isspace((unsigned char)*s)) {
+- errno = EINVAL;
+- return -1;
+- }
+-
+- /* strtod() accepts leading plus signs, but that's ugly, so we don't allow
+- * that. */
+- if (*s == '+') {
++ if (prepare_number_string(s, NUMTYPE_DOUBLE, NULL, &s) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index 9440af917..ed123c796 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -37,6 +37,7 @@
+ #include
+ #include
+ #include
++#include
+
+ #ifndef PACKAGE
+ #error "Please include config.h before including this file!"
+@@ -142,6 +143,8 @@ char *pa_get_state_dir(void);
+ char *pa_get_home_dir_malloc(void);
+ int pa_append_to_home_dir(const char *path, char **_r);
+ int pa_get_config_home_dir(char **_r);
++int pa_get_data_home_dir(char **_r);
++int pa_get_data_dirs(pa_dynarray **_r);
+ int pa_append_to_config_home_dir(const char *path, char **_r);
+ char *pa_get_binary_name_malloc(void);
+ char *pa_runtime_path(const char *fn);
+@@ -151,6 +154,8 @@ int pa_atoi(const char *s, int32_t *ret_i);
+ int pa_atou(const char *s, uint32_t *ret_u);
+ int pa_atol(const char *s, long *ret_l);
+ int pa_atod(const char *s, double *ret_d);
++int pa_atoi64(const char *s, int64_t *ret_l);
++int pa_atou64(const char *s, uint64_t *ret_u);
+
+ size_t pa_snprintf(char *str, size_t size, const char *format, ...);
+ size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap);
+diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
+index c28c5312b..8b830199f 100644
+--- a/src/pulsecore/core.c
++++ b/src/pulsecore/core.c
+@@ -29,15 +29,18 @@
+ #include
+ #include
+ #include
++#include
+
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+ #include
+ #include
++#include
+
+ #include "core.h"
+
+@@ -61,6 +64,46 @@ static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t o
+
+ static void core_free(pa_object *o);
+
++/* Returns a list of handlers. */
++static char *message_handler_list(pa_core *c) {
++ pa_message_params *param;
++ void *state = NULL;
++ struct pa_message_handler *handler;
++
++ param = pa_message_params_new();
++
++ pa_message_params_begin_list(param);
++ PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
++ pa_message_params_begin_list(param);
++
++ /* object_path cannot contain characters that need escaping, therefore
++ * pa_message_params_write_raw() can safely be used here. */
++ pa_message_params_write_raw(param, handler->object_path, true);
++ pa_message_params_write_string(param, handler->description);
++
++ pa_message_params_end_list(param);
++ }
++ pa_message_params_end_list(param);
++
++ return pa_message_params_to_string_free(param);
++}
++
++static int core_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) {
++ pa_core *c;
++
++ pa_assert(c = (pa_core *) userdata);
++ pa_assert(message);
++ pa_assert(response);
++ pa_assert(pa_safe_streq(object_path, "/core"));
++
++ if (pa_streq(message, "list-handlers")) {
++ *response = message_handler_list(c);
++ return PA_OK;
++ }
++
++ return -PA_ERR_NOTIMPLEMENTED;
++}
++
+ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) {
+ pa_core* c;
+ pa_mempool *pool;
+@@ -105,6 +148,8 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t
+ c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
++ pa_message_handler_register(c, "/core", "Core message handler", core_message_handler, (void *) c);
++
+ c->default_source = NULL;
+ c->default_sink = NULL;
+
+@@ -202,6 +247,8 @@ static void core_free(pa_object *o) {
+ pa_assert(pa_hashmap_isempty(c->shared));
+ pa_hashmap_free(c->shared);
+
++ pa_message_handler_unregister(c, "/core");
++
+ pa_assert(pa_hashmap_isempty(c->message_handlers));
+ pa_hashmap_free(c->message_handlers);
+
+diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c
+index a7fbf95d2..5fc22975a 100644
+--- a/src/pulsecore/fdsem.c
++++ b/src/pulsecore/fdsem.c
+@@ -151,26 +151,16 @@ static void flush(pa_fdsem *f) {
+ uint64_t u;
+
+ if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
+-
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+ r = (ssize_t) u;
+ } else
+ #endif
+
+ if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
+-
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+
+ } while (pa_atomic_sub(&f->data->in_pipe, (int) r) > (int) r);
+@@ -194,23 +184,15 @@ void pa_fdsem_post(pa_fdsem *f) {
+ uint64_t u = 1;
+
+ if ((r = pa_write(f->efd, &u, sizeof(u), &f->write_type)) != sizeof(u)) {
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid write to eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid write to eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+ } else
+ #endif
+
+ if ((r = pa_write(f->fds[1], &x, 1, &f->write_type)) != 1) {
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid write to pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid write to pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+
+ break;
+@@ -238,13 +220,8 @@ void pa_fdsem_wait(pa_fdsem *f) {
+ uint64_t u;
+
+ if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
+-
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+
+ r = (ssize_t) u;
+@@ -252,13 +229,8 @@ void pa_fdsem_wait(pa_fdsem *f) {
+ #endif
+
+ if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
+-
+- if (r >= 0 || errno != EINTR) {
+- pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+- pa_assert_not_reached();
+- }
+-
+- continue;
++ pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
++ pa_assert_not_reached();
+ }
+
+ pa_atomic_sub(&f->data->in_pipe, (int) r);
+diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c
+index e25824b78..eb93176ec 100644
+--- a/src/pulsecore/iochannel.c
++++ b/src/pulsecore/iochannel.c
+@@ -227,7 +227,7 @@ ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l) {
+ return r; /* Fast path - we almost always successfully write everything */
+
+ if (r < 0) {
+- if (errno == EINTR || errno == EAGAIN)
++ if (errno == EAGAIN)
+ r = 0;
+ else
+ return r;
+diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build
+index 5f78be012..e8ce2931c 100644
+--- a/src/pulsecore/meson.build
++++ b/src/pulsecore/meson.build
+@@ -182,11 +182,13 @@ libpulsecore_simd = simd.check('libpulsecore_simd',
+ libpulsecore_simd_lib = libpulsecore_simd[0]
+ cdata.merge_from(libpulsecore_simd[1])
+
+-# FIXME: Implement Windows support
+-#'mutex-win32.c',
+-#'poll-win32.c',
+-#'semaphore-win32.c',
+-#'thread-win32.c',
++if host_machine.system() == 'windows'
++ libpulsecore_sources += ['mutex-win32.c',
++ 'poll-win32.c',
++ 'semaphore-win32.c',
++ 'thread-win32.c',
++ ]
++endif
+
+ libpulsecore = shared_library('pulsecore-' + pa_version_major_minor,
+ libpulsecore_sources, libpulsecore_headers,
+@@ -198,7 +200,7 @@ libpulsecore = shared_library('pulsecore-' + pa_version_major_minor,
+ install_rpath : privlibdir,
+ install_dir : privlibdir,
+ link_with : libpulsecore_simd_lib,
+- dependencies : [libm_dep, libpulsecommon_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep, libintl_dep],
++ dependencies : [libm_dep, libpulsecommon_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep, libintl_dep, platform_dep, platform_socket_dep,],
+ implicit_include_directories : false)
+
+ libpulsecore_dep = declare_dependency(link_with: libpulsecore)
+diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
+index 7555a18f1..40644554a 100644
+--- a/src/pulsecore/message-handler.c
++++ b/src/pulsecore/message-handler.c
+@@ -31,6 +31,39 @@
+
+ #include "message-handler.h"
+
++/* Check if a path string starts with a / and only contains valid characters.
++ * Also reject double slashes. */
++static bool object_path_is_valid(const char *test_string) {
++ uint32_t i;
++
++ if (!test_string)
++ return false;
++
++ /* Make sure the string starts with a / */
++ if (test_string[0] != '/')
++ return false;
++
++ for (i = 0; test_string[i]; i++) {
++
++ if ((test_string[i] >= 'a' && test_string[i] <= 'z') ||
++ (test_string[i] >= 'A' && test_string[i] <= 'Z') ||
++ (test_string[i] >= '0' && test_string[i] <= '9') ||
++ test_string[i] == '.' ||
++ test_string[i] == '_' ||
++ test_string[i] == '-' ||
++ (test_string[i] == '/' && test_string[i + 1] != '/'))
++ continue;
++
++ return false;
++ }
++
++ /* Make sure the string does not end with a / */
++ if (test_string[i - 1] == '/')
++ return false;
++
++ return true;
++}
++
+ /* Message handler functions */
+
+ /* Register message handler for the specified object. object_path must be a unique name starting with "/". */
+@@ -42,8 +75,8 @@ void pa_message_handler_register(pa_core *c, const char *object_path, const char
+ pa_assert(cb);
+ pa_assert(userdata);
+
+- /* Ensure that the object path is not empty and starts with "/". */
+- pa_assert(object_path[0] == '/');
++ /* Ensure that object path is valid */
++ pa_assert(object_path_is_valid(object_path));
+
+ handler = pa_xnew0(struct pa_message_handler, 1);
+ handler->userdata = userdata;
+@@ -71,6 +104,8 @@ void pa_message_handler_unregister(pa_core *c, const char *object_path) {
+ /* Send a message to an object identified by object_path */
+ int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) {
+ struct pa_message_handler *handler;
++ int ret;
++ char *parameter_copy, *path_copy;
+
+ pa_assert(c);
+ pa_assert(object_path);
+@@ -79,12 +114,26 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c
+
+ *response = NULL;
+
+- if (!(handler = pa_hashmap_get(c->message_handlers, object_path)))
++ path_copy = pa_xstrdup(object_path);
++
++ /* Remove trailing / from path name if present */
++ if (path_copy[strlen(path_copy) - 1] == '/')
++ path_copy[strlen(path_copy) - 1] = 0;
++
++ if (!(handler = pa_hashmap_get(c->message_handlers, path_copy))) {
++ pa_xfree(path_copy);
+ return -PA_ERR_NOENTITY;
++ }
++
++ parameter_copy = pa_xstrdup(message_parameters);
+
+ /* The handler is expected to return an error code and may also
+ return an error string in response */
+- return handler->callback(handler->object_path, message, message_parameters, response, handler->userdata);
++ ret = handler->callback(handler->object_path, message, parameter_copy, response, handler->userdata);
++
++ pa_xfree(parameter_copy);
++ pa_xfree(path_copy);
++ return ret;
+ }
+
+ /* Set handler description */
+diff --git a/src/pulsecore/message-handler.h b/src/pulsecore/message-handler.h
+index be94510f0..38b24e1b2 100644
+--- a/src/pulsecore/message-handler.h
++++ b/src/pulsecore/message-handler.h
+@@ -26,7 +26,7 @@
+ typedef int (*pa_message_handler_cb_t)(
+ const char *object_path,
+ const char *message,
+- const char *message_parameters,
++ char *message_parameters,
+ char **response,
+ void *userdata);
+
+diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c
+index bce5891db..a707f6c8d 100644
+--- a/src/pulsecore/modargs.c
++++ b/src/pulsecore/modargs.c
+@@ -272,6 +272,15 @@ int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid
+ return parse(ma, args, valid_keys, true);
+ }
+
++int pa_modargs_remove_key(pa_modargs *ma, const char *key) {
++ if (pa_hashmap_remove_and_free(ma->unescaped, key) == 0) {
++ pa_hashmap_remove_and_free(ma->raw, key);
++ return 0;
++ }
++
++ return -1;
++}
++
+ void pa_modargs_free(pa_modargs*ma) {
+ pa_assert(ma);
+
+@@ -544,3 +553,20 @@ const char *pa_modargs_iterate(pa_modargs *ma, void **state) {
+
+ return e->key;
+ }
++
++int pa_modargs_merge_missing(pa_modargs *dst, pa_modargs *src, const char* const valid_keys[]) {
++ void *state;
++ const char *key, *value;
++ int ret = 0;
++
++ for (state = NULL, key = pa_modargs_iterate(src, &state); key; key = pa_modargs_iterate(src, &state)) {
++ value = pa_modargs_get_value(src, key, NULL);
++ if (value && add_key_value(dst, pa_xstrdup(key), pa_xstrdup(value), valid_keys, true) < 0) {
++ pa_log_warn("Failed to add module argument '%s=%s'", key, value);
++ ret = -1;
++ /* continue to gather all errors */
++ }
++ }
++
++ return ret;
++}
+diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h
+index 96132a3aa..abb16747d 100644
+--- a/src/pulsecore/modargs.h
++++ b/src/pulsecore/modargs.h
+@@ -95,4 +95,11 @@ int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa
+ * have any particular order. */
+ const char *pa_modargs_iterate(pa_modargs *ma, void **state);
+
++/* Remove entry by key. Returns 0 if successful, -1 otherwise */
++int pa_modargs_remove_key(pa_modargs *ma, const char *key);
++
++/* Add all key/value pairs from src that are is not already present in dst, to dst.
++ * Returns 0 if there were no errors, -1 otherwise. */
++int pa_modargs_merge_missing(pa_modargs *dst, pa_modargs *src, const char* const valid_keys[]);
++
+ #endif
+diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c
+index 15a54b6ec..040886d2d 100644
+--- a/src/pulsecore/module.c
++++ b/src/pulsecore/module.c
+@@ -45,6 +45,7 @@
+ #define PA_SYMBOL_LOAD_ONCE "pa__load_once"
+ #define PA_SYMBOL_GET_N_USED "pa__get_n_used"
+ #define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated"
++#define PA_SYMBOL_GET_VERSION "pa__get_version"
+
+ bool pa_module_exists(const char *name) {
+ const char *paths, *state = NULL;
+@@ -113,6 +114,7 @@ void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio
+
+ int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) {
+ pa_module *m = NULL;
++ const char *(*get_version)(void);
+ bool (*load_once)(void);
+ const char* (*get_deprecated)(void);
+ pa_modinfo *mi;
+@@ -147,6 +149,21 @@ int pa_module_load(pa_module** module, pa_core *c, const char *name, const char
+ goto fail;
+ }
+
++ if ((get_version = (const char *(*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_VERSION))) {
++ const char *version = get_version();
++
++ if (!pa_safe_streq(version, PACKAGE_VERSION)) {
++ pa_log("Module \"%s\" version (%s) doesn't match the expected version (%s).",
++ name, pa_strnull(version), PACKAGE_VERSION);
++ errcode = -PA_ERR_IO;
++ goto fail;
++ }
++ } else {
++ pa_log("Symbol \"%s\" not found in module \"%s\".", PA_SYMBOL_GET_VERSION, name);
++ errcode = -PA_ERR_IO;
++ goto fail;
++ }
++
+ if ((load_once = (bool (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) {
+
+ m->load_once = load_once();
+diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c
+index a835be1b7..86f029c7c 100644
+--- a/src/pulsecore/mutex-posix.c
++++ b/src/pulsecore/mutex-posix.c
+@@ -25,6 +25,8 @@
+ #include
+
+ #include
++
++#include
+ #include
+
+ #include "mutex.h"
+@@ -103,9 +105,14 @@ bool pa_mutex_try_lock(pa_mutex *m) {
+ }
+
+ void pa_mutex_unlock(pa_mutex *m) {
++ int err;
++
+ pa_assert(m);
+
+- pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0);
++ if ((err = pthread_mutex_unlock(&m->mutex)) != 0) {
++ pa_log("pthread_mutex_unlock() failed: %s", pa_cstrerror(err));
++ pa_assert_not_reached();
++ }
+ }
+
+ pa_cond *pa_cond_new(void) {
+diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
+index 70338b9f3..3de960def 100644
+--- a/src/pulsecore/native-common.h
++++ b/src/pulsecore/native-common.h
+@@ -187,6 +187,9 @@ enum {
+ * BOTH DIRECTIONS */
+ PA_COMMAND_REGISTER_MEMFD_SHMID,
+
++ /* Supported since protocol v34 (14.0) */
++ PA_COMMAND_SEND_OBJECT_MESSAGE,
++
+ PA_COMMAND_MAX
+ };
+
+diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
+index ab632a5ab..c2d26e63e 100644
+--- a/src/pulsecore/pdispatch.c
++++ b/src/pulsecore/pdispatch.c
+@@ -199,6 +199,9 @@ static const char *command_names[PA_COMMAND_MAX] = {
+ /* Supported since protocol v31 (9.0) */
+ /* BOTH DIRECTIONS */
+ [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID",
++
++ /* Supported since protocol v35 (15.0) */
++ [PA_COMMAND_SEND_OBJECT_MESSAGE] = "SEND_OBJECT_MESSAGE",
+ };
+
+ #endif
+diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c
+index f966579b4..16ea9e006 100644
+--- a/src/pulsecore/proplist-util.c
++++ b/src/pulsecore/proplist-util.c
+@@ -45,14 +45,14 @@ extern char **environ;
+
+ #if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF)
+ #include
+-static G_CONST_RETURN gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name);
++static const gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name);
+ #endif
+
+ #if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF)
+ #pragma GCC diagnostic ignored "-Wstrict-prototypes"
+ #include
+ #include
+-static G_CONST_RETURN gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name);
++static const gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name);
+ static Display *_gdk_display PA_GCC_WEAKREF(gdk_display);
+ #endif
+
+diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c
+index d54c7f845..cf0fe4fdf 100644
+--- a/src/pulsecore/protocol-esound.c
++++ b/src/pulsecore/protocol-esound.c
+@@ -1010,7 +1010,7 @@ static int do_read(connection *c) {
+ ((uint8_t*) &c->request) + c->read_data_length,
+ sizeof(c->request) - c->read_data_length)) <= 0) {
+
+- if (r < 0 && (errno == EINTR || errno == EAGAIN))
++ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+@@ -1066,7 +1066,7 @@ static int do_read(connection *c) {
+ (uint8_t*) c->read_data + c->read_data_length,
+ handler->data_length - c->read_data_length)) <= 0) {
+
+- if (r < 0 && (errno == EINTR || errno == EAGAIN))
++ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+@@ -1097,7 +1097,7 @@ static int do_read(connection *c) {
+ pa_memblock_release(c->scache.memchunk.memblock);
+
+ if (r <= 0) {
+- if (r < 0 && (errno == EINTR || errno == EAGAIN))
++ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+@@ -1165,7 +1165,7 @@ static int do_read(connection *c) {
+
+ if (r <= 0) {
+
+- if (r < 0 && (errno == EINTR || errno == EAGAIN))
++ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
+index e8559b239..f8dad57d6 100644
+--- a/src/pulsecore/protocol-native.c
++++ b/src/pulsecore/protocol-native.c
+@@ -47,6 +47,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -4721,6 +4722,55 @@ static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag,
+ protocol_error(c);
+ }
+
++/* Send message to an object which registered a handler. Result must be returned as string. */
++static void command_send_object_message(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
++ const char *object_path = NULL;
++ const char *message = NULL;
++ const char *message_parameters = NULL;
++ const char *client_name;
++ char *response = NULL;
++ int ret;
++ pa_tagstruct *reply;
++
++ pa_native_connection_assert_ref(c);
++ pa_assert(t);
++
++ if (pa_tagstruct_gets(t, &object_path) < 0 ||
++ pa_tagstruct_gets(t, &message) < 0 ||
++ pa_tagstruct_gets(t, &message_parameters) < 0 ||
++ !pa_tagstruct_eof(t)) {
++ protocol_error(c);
++ return;
++ }
++
++ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
++ CHECK_VALIDITY(c->pstream, object_path != NULL, tag, PA_ERR_INVALID);
++ CHECK_VALIDITY(c->pstream, pa_utf8_valid(object_path), tag, PA_ERR_INVALID);
++ CHECK_VALIDITY(c->pstream, message != NULL, tag, PA_ERR_INVALID);
++ CHECK_VALIDITY(c->pstream, pa_utf8_valid(message), tag, PA_ERR_INVALID);
++ if (message_parameters)
++ CHECK_VALIDITY(c->pstream, pa_utf8_valid(message_parameters), tag, PA_ERR_INVALID);
++
++ client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
++ pa_log_debug("Client %s sent message %s to path %s", client_name, message, object_path);
++ if (message_parameters)
++ pa_log_debug("Message parameters: %s", message_parameters);
++
++ ret = pa_message_handler_send_message(c->protocol->core, object_path, message, message_parameters, &response);
++
++ if (ret < 0) {
++ pa_pstream_send_error(c->pstream, tag, -ret);
++ return;
++ }
++
++ reply = reply_new(tag);
++ pa_tagstruct_puts(reply, response);
++ pa_xfree(response);
++
++ pa_pstream_send_tagstruct(c->pstream, reply);
++}
++
+ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+@@ -4972,6 +5022,8 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+
+ [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid,
+
++ [PA_COMMAND_SEND_OBJECT_MESSAGE] = command_send_object_message,
++
+ [PA_COMMAND_EXTENSION] = command_extension
+ };
+
+diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c
+index 77d05398c..ed6a402e7 100644
+--- a/src/pulsecore/protocol-simple.c
++++ b/src/pulsecore/protocol-simple.c
+@@ -183,7 +183,7 @@ static int do_read(connection *c) {
+
+ if (r <= 0) {
+
+- if (r < 0 && (errno == EINTR || errno == EAGAIN))
++ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno));
+diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c
+index eb7050828..7147b776a 100644
+--- a/src/pulsecore/pstream.c
++++ b/src/pulsecore/pstream.c
+@@ -154,6 +154,7 @@ struct pa_pstream {
+ * @registered_memfd_ids: registered memfd pools SHM IDs. Check
+ * pa_pstream_register_memfd_mempool() for more information. */
+ bool use_shm, use_memfd;
++ bool non_registered_memfd_id_error_logged;
+ pa_idxset *registered_memfd_ids;
+
+ pa_memimport *import;
+@@ -677,9 +678,11 @@ static void prepare_next_write_item(pa_pstream *p) {
+ flags |= PA_FLAG_SHMDATA_MEMFD_BLOCK;
+ send_payload = false;
+ } else {
+- if (pa_log_ratelimit(PA_LOG_ERROR)) {
++ if (!p->non_registered_memfd_id_error_logged) {
+ pa_log("Cannot send block reference with non-registered memfd ID = %u", shm_id);
+- pa_log("Fallig back to copying full block data over socket");
++ pa_log("Falling back to copying full block data over socket");
++ pa_log("There's a bug report about this: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/824");
++ p->non_registered_memfd_id_error_logged = true;
+ }
+ }
+ }
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index e89b59655..e8aeb17fa 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -3577,6 +3577,14 @@ unsigned pa_device_init_priority(pa_proplist *p) {
+
+ pa_assert(p);
+
++ /* JACK sinks and sources get very high priority so that we'll switch the
++ * default devices automatically when jackd starts and
++ * module-jackdbus-detect creates the jack sink and source. */
++ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_API))) {
++ if (pa_streq(s, "jack"))
++ priority += 10000;
++ }
++
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) {
+
+ if (pa_streq(s, "sound"))
+@@ -3609,10 +3617,18 @@ unsigned pa_device_init_priority(pa_proplist *p) {
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) {
+
+- if (pa_startswith(s, "analog-"))
++ if (pa_startswith(s, "analog-")) {
+ priority += 9;
++
++ /* If an analog device has an intended role of "phone", it probably
++ * co-exists with another device that is meant for everything else,
++ * and that other device should have higher priority than the phone
++ * device. */
++ if (pa_str_in_list_spaces(pa_proplist_gets(p, PA_PROP_DEVICE_INTENDED_ROLES), "phone"))
++ priority -= 1;
++ }
+ else if (pa_startswith(s, "iec958-"))
+- priority += 8;
++ priority += 7;
+ }
+
+ return priority;
+diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c
+index e389ef203..83d4c9c6c 100644
+--- a/src/pulsecore/socket-util.c
++++ b/src/pulsecore/socket-util.c
+@@ -239,8 +239,13 @@ int pa_unix_socket_is_stale(const char *fn) {
+ sa.sun_path[sizeof(sa.sun_path) - 1] = 0;
+
+ if (connect(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
++#if !defined(OS_IS_WIN32)
+ if (errno == ECONNREFUSED)
+ ret = 1;
++#else
++ if (WSAGetLastError() == WSAECONNREFUSED)
++ ret = 1;
++#endif
+ } else
+ ret = 0;
+
+diff --git a/src/pulsecore/socket.h b/src/pulsecore/socket.h
+index 72f222818..982901b0e 100644
+--- a/src/pulsecore/socket.h
++++ b/src/pulsecore/socket.h
+@@ -11,6 +11,31 @@
+
+ typedef long suseconds_t;
+
++/** Windows 10 supports AF_UNIX as of build 17603, with
++ support provided in the header file . However,
++ only the latest Windows SDK provides this file; older SDKs and
++ MinGW do not.
++
++ Hence we define SOCKADDR_UN here. We do not expect this definition to change
++ as Windows has some pretty good binary backwards-compatibility guarantees.
++
++ This shouldn't pose a problem for older versions of Windows; we expect them to
++ fail with an error whenever we try to make a socket of type AF_UNIX. */
++#define UNIX_PATH_MAX 108
++
++typedef struct sockaddr_un
++{
++ ADDRESS_FAMILY sun_family; /* AF_UNIX */
++ char sun_path[UNIX_PATH_MAX]; /* pathname */
++} SOCKADDR_UN, *PSOCKADDR_UN;
++
++#ifndef SUN_LEN
++#define SUN_LEN(ptr) \
++ ((size_t)(((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path))
++#endif
++
++#define HAVE_SYS_UN_H
++
+ #endif
+
+ #ifdef HAVE_WS2TCPIP_H
+diff --git a/src/pulsecore/x11wrap.c b/src/pulsecore/x11wrap.c
+index 0c040cf57..635d21e00 100644
+--- a/src/pulsecore/x11wrap.c
++++ b/src/pulsecore/x11wrap.c
+@@ -33,6 +33,8 @@
+
+ #include "x11wrap.h"
+
++#include
++
+ typedef struct pa_x11_internal pa_x11_internal;
+
+ struct pa_x11_internal {
+@@ -51,6 +53,7 @@ struct pa_x11_wrapper {
+
+ pa_defer_event* defer_event;
+ pa_io_event* io_event;
++ pa_defer_event* cleanup_event;
+
+ PA_LLIST_HEAD(pa_x11_client, clients);
+ PA_LLIST_HEAD(pa_x11_internal, internals);
+@@ -64,6 +67,8 @@ struct pa_x11_client {
+ void *userdata;
+ };
+
++static void x11_wrapper_kill(pa_x11_wrapper *w);
++
+ /* Dispatch all pending X11 events */
+ static void work(pa_x11_wrapper *w) {
+ pa_assert(w);
+@@ -167,6 +172,38 @@ static void x11_watch(Display *display, XPointer userdata, int fd, Bool opening,
+ x11_internal_remove(w, (pa_x11_internal*) *watch_data);
+ }
+
++static int x11_error_handler(Display* display, XErrorEvent* error_event) {
++ pa_log_warn("X11 error handler called");
++ return 0;
++}
++
++static int x11_io_error_handler(Display* display) {
++ pa_log_warn("X11 I/O error handler called");
++ return 0;
++}
++
++static void deferred_x11_teardown(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
++ pa_x11_wrapper *w = userdata;
++
++ m->defer_enable(e, 0);
++
++ pa_log_debug("Start tearing down X11 modules after X11 I/O error");
++
++ x11_wrapper_kill(w);
++
++ pa_log_debug("Done tearing down X11 modules after X11 I/O error");
++}
++
++#ifdef HAVE_XSETIOERROREXITHANDLER
++static void x11_io_error_exit_handler(Display* display, void *userdata) {
++ pa_x11_wrapper *w = userdata;
++
++ pa_log_warn("X11 I/O error exit handler called, preparing to tear down X11 modules");
++
++ pa_x11_wrapper_kill_deferred(w);
++}
++#endif
++
+ static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char *t) {
+ pa_x11_wrapper*w;
+ Display *d;
+@@ -187,11 +224,20 @@ static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char
+
+ w->defer_event = c->mainloop->defer_new(c->mainloop, defer_event, w);
+ w->io_event = c->mainloop->io_new(c->mainloop, ConnectionNumber(d), PA_IO_EVENT_INPUT, display_io_event, w);
++ w->cleanup_event = c->mainloop->defer_new(c->mainloop, deferred_x11_teardown, w);
++ w->core->mainloop->defer_enable(w->cleanup_event, 0);
+
++ XSetErrorHandler(x11_error_handler);
++ XSetIOErrorHandler(x11_io_error_handler);
++#ifdef HAVE_XSETIOERROREXITHANDLER
++ XSetIOErrorExitHandler(d, x11_io_error_exit_handler, w);
++#endif
+ XAddConnectionWatch(d, x11_watch, (XPointer) w);
+
+ pa_assert_se(pa_shared_set(c, w->property_name, w) >= 0);
+
++ pa_log_debug("Created X11 connection wrapper '%s'", w->property_name);
++
+ return w;
+ }
+
+@@ -202,9 +248,12 @@ static void x11_wrapper_free(pa_x11_wrapper*w) {
+
+ pa_assert(!w->clients);
+
++ pa_log_debug("Destroying X11 connection wrapper '%s'", w->property_name);
++
+ XRemoveConnectionWatch(w->display, x11_watch, (XPointer) w);
+ XCloseDisplay(w->display);
+
++ w->core->mainloop->defer_free(w->cleanup_event);
+ w->core->mainloop->io_free(w->io_event);
+ w->core->mainloop->defer_free(w->defer_event);
+
+@@ -261,7 +310,15 @@ xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w) {
+ return XGetXCBConnection(pa_x11_wrapper_get_display(w));
+ }
+
+-void pa_x11_wrapper_kill(pa_x11_wrapper *w) {
++void pa_x11_wrapper_kill_deferred(pa_x11_wrapper *w) {
++ pa_assert(w);
++
++ /* schedule X11 display teardown */
++ w->core->mainloop->defer_enable(w->cleanup_event, 1);
++}
++
++/* Kill the connection to the X11 display */
++static void x11_wrapper_kill(pa_x11_wrapper *w) {
+ pa_x11_client *c, *n;
+
+ pa_assert(w);
+diff --git a/src/pulsecore/x11wrap.h b/src/pulsecore/x11wrap.h
+index 0539303c2..2d81705c2 100644
+--- a/src/pulsecore/x11wrap.h
++++ b/src/pulsecore/x11wrap.h
+@@ -48,8 +48,8 @@ Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w);
+ /* Return the XCB connection object for this connection */
+ xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w);
+
+-/* Kill the connection to the X11 display */
+-void pa_x11_wrapper_kill(pa_x11_wrapper *w);
++/* Initiate X11 connection teardown. */
++void pa_x11_wrapper_kill_deferred(pa_x11_wrapper *w);
+
+ /* Register an X11 client, that is called for each X11 event */
+ pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata);
+diff --git a/src/tests/alsa-mixer-path-test.c b/src/tests/alsa-mixer-path-test.c
+index 75cf08613..91e4d0dfa 100644
+--- a/src/tests/alsa-mixer-path-test.c
++++ b/src/tests/alsa-mixer-path-test.c
+@@ -17,7 +17,6 @@
+ * Meson. */
+ #ifndef MESON_BUILD
+
+-/* This function was copied from alsa-mixer.c */
+ static const char *get_default_paths_dir(void) {
+ if (pa_run_from_build_tree())
+ return PA_SRCDIR "/modules/alsa/mixer/paths/";
+diff --git a/src/tests/core-util-test.c b/src/tests/core-util-test.c
+index 8d1db0c07..49aa4abcc 100644
+--- a/src/tests/core-util-test.c
++++ b/src/tests/core-util-test.c
+@@ -26,7 +26,7 @@
+ #include
+ #include
+
+-START_TEST (modargs_test_parse_boolean) {
++START_TEST (test_parse_boolean) {
+ ck_assert_int_eq(pa_parse_boolean("true"), true);
+ ck_assert_int_eq(pa_parse_boolean("yes"), true);
+ ck_assert_int_eq(pa_parse_boolean("1"), true);
+@@ -40,7 +40,7 @@ START_TEST (modargs_test_parse_boolean) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_parse_volume) {
++START_TEST (test_parse_volume) {
+ pa_volume_t value;
+
+ // dB volumes
+@@ -92,7 +92,7 @@ START_TEST (modargs_test_parse_volume) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_atoi) {
++START_TEST (test_atoi) {
+ int32_t value;
+
+ // decimal
+@@ -100,6 +100,10 @@ START_TEST (modargs_test_atoi) {
+ ck_assert_int_eq(value, 100000);
+ ck_assert_int_eq(pa_atoi("-100000", &value), 0);
+ ck_assert_int_eq(value, -100000);
++ ck_assert_int_eq(pa_atoi("010", &value), 0);
++ ck_assert_int_eq(value, 10);
++ ck_assert_int_eq(pa_atoi("-010", &value), 0);
++ ck_assert_int_eq(value, -10);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atoi("0x100000", &value), 0);
+@@ -111,15 +115,18 @@ START_TEST (modargs_test_atoi) {
+ ck_assert_int_lt(pa_atoi("3.14", &value), 0);
+ ck_assert_int_lt(pa_atoi("7*8", &value), 0);
+ ck_assert_int_lt(pa_atoi("false", &value), 0);
++ ck_assert_int_lt(pa_atoi("10000000000", &value), 0);
+ }
+ END_TEST
+
+-START_TEST (modargs_test_atou) {
++START_TEST (test_atou) {
+ uint32_t value;
+
+ // decimal
+ ck_assert_int_eq(pa_atou("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
++ ck_assert_int_eq(pa_atou("010", &value), 0);
++ ck_assert_int_eq(value, 10);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atou("0x100000", &value), 0);
+@@ -131,10 +138,35 @@ START_TEST (modargs_test_atou) {
+ ck_assert_int_lt(pa_atou("3.14", &value), 0);
+ ck_assert_int_lt(pa_atou("7*8", &value), 0);
+ ck_assert_int_lt(pa_atou("false", &value), 0);
++ ck_assert_int_lt(pa_atou("10000000000", &value), 0);
+ }
+ END_TEST
+
+-START_TEST (modargs_test_atol) {
++START_TEST (test_atou64) {
++ uint64_t value;
++
++ // decimal
++ ck_assert_int_eq(pa_atou64("100000", &value), 0);
++ ck_assert_int_eq(value, 100000);
++ ck_assert_int_eq(pa_atou64("010", &value), 0);
++ ck_assert_int_eq(value, 10);
++ ck_assert_int_eq(pa_atou64("10000000000", &value), 0);
++ ck_assert_int_eq(value, 10000000000);
++
++ // hexadecimal
++ ck_assert_int_eq(pa_atou64("0x100000", &value), 0);
++ ck_assert_int_eq(value, 0x100000);
++
++ // invalid values
++ ck_assert_int_lt(pa_atou64("-100000", &value), 0);
++ ck_assert_int_lt(pa_atou64("-0x100000", &value), 0);
++ ck_assert_int_lt(pa_atou64("3.14", &value), 0);
++ ck_assert_int_lt(pa_atou64("7*8", &value), 0);
++ ck_assert_int_lt(pa_atou64("false", &value), 0);
++}
++END_TEST
++
++START_TEST (test_atol) {
+ long value;
+
+ // decimal
+@@ -142,6 +174,10 @@ START_TEST (modargs_test_atol) {
+ ck_assert_int_eq(value, 100000l);
+ ck_assert_int_eq(pa_atol("-100000", &value), 0);
+ ck_assert_int_eq(value, -100000l);
++ ck_assert_int_eq(pa_atol("010", &value), 0);
++ ck_assert_int_eq(value, 10);
++ ck_assert_int_eq(pa_atol("-010", &value), 0);
++ ck_assert_int_eq(value, -10);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atol("0x100000", &value), 0);
+@@ -156,7 +192,35 @@ START_TEST (modargs_test_atol) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_atod) {
++START_TEST (test_atoi64) {
++ int64_t value;
++
++ // decimal
++ ck_assert_int_eq(pa_atoi64("100000", &value), 0);
++ ck_assert_int_eq(value, 100000);
++ ck_assert_int_eq(pa_atoi64("-100000", &value), 0);
++ ck_assert_int_eq(value, -100000);
++ ck_assert_int_eq(pa_atoi64("010", &value), 0);
++ ck_assert_int_eq(value, 10);
++ ck_assert_int_eq(pa_atoi64("-010", &value), 0);
++ ck_assert_int_eq(value, -10);
++ ck_assert_int_eq(pa_atoi64("10000000000", &value), 0);
++ ck_assert_int_eq(value, 10000000000);
++
++ // hexadecimal
++ ck_assert_int_eq(pa_atoi64("0x100000", &value), 0);
++ ck_assert_int_eq(value, 0x100000);
++ ck_assert_int_eq(pa_atoi64("-0x100000", &value), 0);
++ ck_assert_int_eq(value, -0x100000);
++
++ // invalid values
++ ck_assert_int_lt(pa_atoi64("3.14", &value), 0);
++ ck_assert_int_lt(pa_atoi64("7*8", &value), 0);
++ ck_assert_int_lt(pa_atoi64("false", &value), 0);
++}
++END_TEST
++
++START_TEST (test_atod) {
+ double value;
+ double epsilon = 0.001;
+
+@@ -177,7 +241,7 @@ START_TEST (modargs_test_atod) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_replace) {
++START_TEST (test_replace) {
+ char* value;
+
+ value = pa_replace("abcde", "bcd", "XYZ");
+@@ -198,22 +262,22 @@ START_TEST (modargs_test_replace) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_replace_fail_1) {
++START_TEST (test_replace_fail_1) {
+ pa_replace(NULL, "b", "bab");
+ }
+ END_TEST
+
+-START_TEST (modargs_test_replace_fail_2) {
++START_TEST (test_replace_fail_2) {
+ pa_replace("abe", NULL, "bab");
+ }
+ END_TEST
+
+-START_TEST (modargs_test_replace_fail_3) {
++START_TEST (test_replace_fail_3) {
+ pa_replace("abcde", "b", NULL);
+ }
+ END_TEST
+
+-START_TEST (modargs_test_escape) {
++START_TEST (test_escape) {
+ char* value;
+
+ value = pa_escape("abcde", "bcd");
+@@ -230,12 +294,12 @@ START_TEST (modargs_test_escape) {
+ }
+ END_TEST
+
+-START_TEST (modargs_test_replace_fail_4) {
++START_TEST (test_replace_fail_4) {
+ pa_replace("abe", "", "bab");
+ }
+ END_TEST
+
+-START_TEST (modargs_test_unescape) {
++START_TEST (test_unescape) {
+ char* value;
+
+ value = pa_unescape(pa_xstrdup("a\\b\\c\\de"));
+@@ -261,19 +325,21 @@ int main(int argc, char *argv[]) {
+
+ tc = tcase_create("core-util");
+ suite_add_tcase(s, tc);
+- tcase_add_test(tc, modargs_test_parse_boolean);
+- tcase_add_test(tc, modargs_test_parse_volume);
+- tcase_add_test(tc, modargs_test_atoi);
+- tcase_add_test(tc, modargs_test_atou);
+- tcase_add_test(tc, modargs_test_atol);
+- tcase_add_test(tc, modargs_test_atod);
+- tcase_add_test(tc, modargs_test_replace);
+- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_1, SIGABRT);
+- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_2, SIGABRT);
+- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_3, SIGABRT);
+- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_4, SIGABRT);
+- tcase_add_test(tc, modargs_test_escape);
+- tcase_add_test(tc, modargs_test_unescape);
++ tcase_add_test(tc, test_parse_boolean);
++ tcase_add_test(tc, test_parse_volume);
++ tcase_add_test(tc, test_atoi);
++ tcase_add_test(tc, test_atou);
++ tcase_add_test(tc, test_atou64);
++ tcase_add_test(tc, test_atol);
++ tcase_add_test(tc, test_atoi64);
++ tcase_add_test(tc, test_atod);
++ tcase_add_test(tc, test_replace);
++ tcase_add_test_raise_signal(tc, test_replace_fail_1, SIGABRT);
++ tcase_add_test_raise_signal(tc, test_replace_fail_2, SIGABRT);
++ tcase_add_test_raise_signal(tc, test_replace_fail_3, SIGABRT);
++ tcase_add_test_raise_signal(tc, test_replace_fail_4, SIGABRT);
++ tcase_add_test(tc, test_escape);
++ tcase_add_test(tc, test_unescape);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+diff --git a/src/tests/once-test.c b/src/tests/once-test.c
+index cb5618707..c4d4b4be6 100644
+--- a/src/tests/once-test.c
++++ b/src/tests/once-test.c
+@@ -22,10 +22,8 @@
+ #ifdef HAVE_PTHREAD
+ #include
+ #ifdef HAVE_PTHREAD_SETAFFINITY_NP
+-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ #ifdef __FreeBSD__
+ #include
+-#endif
+ #include
+ #include
+ #endif
+@@ -63,7 +61,7 @@ static void thread_func(void *data) {
+
+ #ifdef HAVE_PTHREAD_SETAFFINITY_NP
+ static pa_atomic_t i_cpu = PA_ATOMIC_INIT(0);
+-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
++#ifdef __FreeBSD__
+ cpuset_t mask;
+ #else
+ cpu_set_t mask;
+diff --git a/src/tests/rtstutter.c b/src/tests/rtstutter.c
+index 56b5146ca..9d74855a3 100644
+--- a/src/tests/rtstutter.c
++++ b/src/tests/rtstutter.c
+@@ -29,10 +29,8 @@
+ #ifdef HAVE_PTHREAD
+ #include
+ #ifdef HAVE_PTHREAD_SETAFFINITY_NP
+-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ #ifdef __FreeBSD__
+ #include
+-#endif
+ #include
+ #include
+ #endif
+@@ -61,7 +59,7 @@ static void work(void *p) {
+
+ #ifdef HAVE_PTHREAD_SETAFFINITY_NP
+ {
+-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
++#ifdef __FreeBSD__
+ cpuset_t mask;
+ #else
+ cpu_set_t mask;
+diff --git a/src/utils/meson.build b/src/utils/meson.build
+index dedf4e404..f40eaff95 100644
+--- a/src/utils/meson.build
++++ b/src/utils/meson.build
+@@ -19,12 +19,14 @@ executable('pacat',
+ c_args : pa_c_args,
+ )
+
+-foreach alias : pacat_aliases
+- # FIXME How to handle extension (.exe on windows)?
+- dst = join_paths(bindir, alias)
+- cmd = 'ln -fs @0@ $DESTDIR@1@'.format('pacat', dst)
+- meson.add_install_script('sh', '-c', cmd)
+-endforeach
++# Windows doesn't support symbolic links.
++if host_machine.system() != 'windows'
++ foreach alias : pacat_aliases
++ dst = join_paths(bindir, alias)
++ cmd = 'ln -fs @0@ $DESTDIR@1@'.format('pacat', dst)
++ meson.add_install_script('sh', '-c', cmd)
++ endforeach
++endif
+
+ pactl_sources = [
+ 'pactl.c',
+@@ -40,33 +42,35 @@ executable('pactl',
+ c_args : pa_c_args,
+ )
+
+-pasuspender_sources = [
+- 'pasuspender.c',
+-]
++if host_machine.system() != 'windows'
++ pasuspender_sources = [
++ 'pasuspender.c',
++ ]
+
+-executable('pasuspender',
+- pasuspender_sources,
+- install: true,
+- install_rpath : privlibdir,
+- include_directories : [configinc, topinc],
+- link_with : [libpulsecommon, libpulse],
+- dependencies: [libintl_dep],
+- c_args : pa_c_args,
+-)
++ executable('pasuspender',
++ pasuspender_sources,
++ install: true,
++ install_rpath : privlibdir,
++ include_directories : [configinc, topinc],
++ link_with : [libpulsecommon, libpulse],
++ dependencies: [libintl_dep],
++ c_args : pa_c_args,
++ )
+
+-pacmd_sources = [
+- 'pacmd.c',
+-]
++ pacmd_sources = [
++ 'pacmd.c',
++ ]
+
+-executable('pacmd',
+- pacmd_sources,
+- install: true,
+- install_rpath : privlibdir,
+- include_directories : [configinc, topinc],
+- link_with : [libpulsecommon, libpulse],
+- dependencies: [libintl_dep],
+- c_args : pa_c_args,
+-)
++ executable('pacmd',
++ pacmd_sources,
++ install: true,
++ install_rpath : privlibdir,
++ include_directories : [configinc, topinc],
++ link_with : [libpulsecommon, libpulse],
++ dependencies: [libintl_dep],
++ c_args : pa_c_args,
++ )
++endif
+
+ if x11_dep.found()
+ pax11publish_sources = [
+@@ -84,7 +88,7 @@ if x11_dep.found()
+ )
+ endif
+
+-if cc.has_header('sys/soundcard.h')
++if cdata.has('HAVE_OSS_WRAPPER')
+ libpulsecommon_sources = [
+ 'padsp.c',
+ ]
+diff --git a/src/utils/pa-info b/src/utils/pa-info
+index 1b1cc29b9..7bee1d8df 100755
+--- a/src/utils/pa-info
++++ b/src/utils/pa-info
+@@ -40,6 +40,7 @@ function jacks_do {
+ function alsa_info_do {
+ alsa_info=$(which alsa-info.sh)
+ [ $alsa_info ] || alsa_info=$(which alsa-info)
++ [ $alsa_info ] || alsa_info='/usr/sbin/alsa-info.sh'
+ [ $alsa_info ] || alsa_info='/usr/share/alsa-base/alsa-info.sh'
+ [ -f $alsa_info ] && {
+ $alsa_info --stdout
+diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c
+index 616573cc6..6eae6c427 100644
+--- a/src/utils/pacmd.c
++++ b/src/utils/pacmd.c
+@@ -77,6 +77,7 @@ static void help(const char *argv0) {
+ printf("%s %s %s\n", argv0, "set-log-meta", _("1|0"));
+ printf("%s %s %s\n", argv0, "set-log-time", _("1|0"));
+ printf("%s %s %s\n", argv0, "set-log-backtrace", _("FRAMES"));
++ printf("%s %s %s\n", argv0, "send-message", _("RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"));
+
+ printf(_("\n"
+ " -h, --help Show this help\n"
+diff --git a/src/utils/pactl.c b/src/utils/pactl.c
+index 9af1fc521..bc1c5265a 100644
+--- a/src/utils/pactl.c
++++ b/src/utils/pactl.c
+@@ -35,6 +35,7 @@
+ #include
+
+ #include
++#include
+ #include
+
+ #include
+@@ -56,7 +57,10 @@ static char
+ *card_name = NULL,
+ *profile_name = NULL,
+ *port_name = NULL,
+- *formats = NULL;
++ *formats = NULL,
++ *object_path = NULL,
++ *message = NULL,
++ *message_args = NULL;
+
+ static uint32_t
+ sink_input_idx = PA_INVALID_INDEX,
+@@ -130,6 +134,7 @@ static enum {
+ SET_SOURCE_OUTPUT_MUTE,
+ SET_SINK_FORMATS,
+ SET_PORT_LATENCY_OFFSET,
++ SEND_MESSAGE,
+ SUBSCRIBE
+ } action = NONE;
+
+@@ -878,6 +883,75 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
+ complete_action();
+ }
+
++static void send_message_callback(pa_context *c, int success, char *response, void *userdata) {
++
++ if (!success) {
++ pa_log(_("Send message failed: %s"), pa_strerror(pa_context_errno(c)));
++ quit(1);
++ return;
++ }
++
++ printf("%s\n", response);
++
++ complete_action();
++}
++
++static void list_handlers_callback(pa_context *c, int success, char *response, void *userdata) {
++ void *state = NULL;
++ char *handler_list;
++ char *handler_struct;
++ int err;
++
++ if (!success) {
++ pa_log(_("list-handlers message failed: %s"), pa_strerror(pa_context_errno(c)));
++ quit(1);
++ return;
++ }
++
++ if (pa_message_params_read_raw(response, &handler_list, &state) <= 0) {
++ pa_log(_("list-handlers message response could not be parsed correctly"));
++ quit(1);
++ return;
++ }
++
++ state = NULL;
++ while ((err = pa_message_params_read_raw(handler_list, &handler_struct, &state)) > 0) {
++ void *state2 = NULL;
++ const char *path;
++ const char *description;
++
++ if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) {
++ err = -1;
++ break;
++ }
++ if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) {
++ err = -1;
++ break;
++ }
++
++ if (short_list_format)
++ printf("%s\n", path);
++ else {
++ if (nl)
++ printf("\n");
++ nl = true;
++
++ printf("Message Handler %s\n"
++ "\tDescription: %s\n",
++ path,
++ description);
++ }
++ }
++
++ if (err < 0) {
++ pa_log(_("list-handlers message response could not be parsed correctly"));
++ quit(1);
++ return;
++ }
++
++ complete_action();
++}
++
+ static void volume_relative_adjust(pa_cvolume *cv) {
+ pa_assert(volume_flags & VOL_RELATIVE);
+
+@@ -1291,6 +1365,8 @@ static void context_state_callback(pa_context *c, void *userdata) {
+ o = pa_context_get_sample_info_list(c, get_sample_info_callback, NULL);
+ else if (pa_streq(list_type, "cards"))
+ o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
++ else if (pa_streq(list_type, "message-handlers"))
++ o = pa_context_send_message_to_object(c, "/core", "list-handlers", NULL, list_handlers_callback, NULL);
+ else
+ pa_assert_not_reached();
+ } else {
+@@ -1450,6 +1526,10 @@ static void context_state_callback(pa_context *c, void *userdata) {
+ o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL);
+ break;
+
++ case SEND_MESSAGE:
++ o = pa_context_send_message_to_object(c, object_path, message, message_args, send_message_callback, NULL);
++ break;
++
+ case SUBSCRIBE:
+ pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL);
+
+@@ -1626,6 +1706,7 @@ static void help(const char *argv0) {
+ printf("%s %s %s %s\n", argv0, _("[options]"), "set-(sink-input|source-output)-mute", _("#N 1|0|toggle"));
+ printf("%s %s %s %s\n", argv0, _("[options]"), "set-sink-formats", _("#N FORMATS"));
+ printf("%s %s %s %s\n", argv0, _("[options]"), "set-port-latency-offset", _("CARD-NAME|CARD-#N PORT OFFSET"));
++ printf("%s %s %s %s\n", argv0, _("[options]"), "send-message", _("RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"));
+ printf("%s %s %s\n", argv0, _("[options]"), "subscribe");
+ printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n"
+ "can be used to specify the default sink, source and monitor.\n"));
+@@ -1722,12 +1803,13 @@ int main(int argc, char *argv[]) {
+ if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
+ pa_streq(argv[i], "sinks") || pa_streq(argv[i], "sink-inputs") ||
+ pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
+- pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards")) {
++ pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") ||
++ pa_streq(argv[i], "message-handlers")) {
+ list_type = pa_xstrdup(argv[i]);
+ } else if (pa_streq(argv[i], "short")) {
+ short_list_format = true;
+ } else {
+- pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards");
++ pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards, message-handlers");
+ goto quit;
+ }
+ }
+@@ -2061,6 +2143,22 @@ int main(int argc, char *argv[]) {
+ goto quit;
+ }
+
++ } else if (pa_streq(argv[optind], "send-message")) {
++ action = SEND_MESSAGE;
++
++ if (argc < optind+3) {
++ pa_log(_("You have to specify at least an object path and a message name"));
++ goto quit;
++ }
++
++ object_path = pa_xstrdup(argv[optind + 1]);
++ message = pa_xstrdup(argv[optind + 2]);
++ if (argc >= optind+4)
++ message_args = pa_xstrdup(argv[optind + 3]);
++
++ if (argc > optind+4)
++ pa_log(_("Excess arguments given, they will be ignored. Note that all message parameters must be given as a single string."));
++
+ } else if (pa_streq(argv[optind], "subscribe"))
+
+ action = SUBSCRIBE;
+@@ -2154,6 +2252,9 @@ quit:
+ pa_xfree(profile_name);
+ pa_xfree(port_name);
+ pa_xfree(formats);
++ pa_xfree(object_path);
++ pa_xfree(message);
++ pa_xfree(message_args);
+
+ if (sndfile)
+ sf_close(sndfile);
From ff0a8f391304db1aaf5f4ff3fa0d59a441740026 Mon Sep 17 00:00:00 2001
From: Eric Lemanissier
Date: Fri, 8 Jan 2021 21:29:24 +0100
Subject: [PATCH 5/6] migrate to meson
---
recipes/pulseaudio/all/conanfile.py | 58 +++++++++++++++++------------
1 file changed, 35 insertions(+), 23 deletions(-)
diff --git a/recipes/pulseaudio/all/conanfile.py b/recipes/pulseaudio/all/conanfile.py
index 2fe84f32b13d2..6e836372721dc 100644
--- a/recipes/pulseaudio/all/conanfile.py
+++ b/recipes/pulseaudio/all/conanfile.py
@@ -1,4 +1,4 @@
-from conans import ConanFile, tools, AutoToolsBuildEnvironment, RunEnvironment
+from conans import ConanFile, tools, Meson, RunEnvironment
from conans.errors import ConanInvalidConfiguration
import os
@@ -24,17 +24,21 @@ class PulseAudioConan(ConanFile):
"with_openssl": [True, False],
# FIXME: enable when #2147 is merged
# "with_dbus": [True, False],
+ "database": ['gdbm', 'tdb', 'simple'],
+ "bluez5": [True, False],
}
default_options = {
"shared": False,
"fPIC": True,
"with_alsa": True,
"with_glib": False,
- "with_fftw": True,
+ "with_fftw": False,
"with_x11": True,
"with_openssl": True,
# FIXME: enable when #2147 is merged
# "with_dbus": False,
+ "database": 'simple',
+ "bluez5": False,
}
build_requires = "gettext/0.20.1", "libtool/2.4.6"
@@ -45,7 +49,11 @@ class PulseAudioConan(ConanFile):
def _source_subfolder(self):
return "source_subfolder"
- _autotools = None
+ @property
+ def _build_subfolder(self):
+ return "build_subfolder"
+
+ _meson = None
def config_options(self):
if self.settings.os == "Windows":
@@ -61,6 +69,9 @@ def configure(self):
del self.settings.compiler.libcxx
del self.settings.compiler.cppstd
+ def build_requirements(self):
+ self.build_requires("meson/0.56.1")
+
def requirements(self):
self.requires("libsndfile/1.0.30")
@@ -85,40 +96,41 @@ def source(self):
extracted_dir = self.name + "-" + self.version
os.rename(extracted_dir, self._source_subfolder)
- def _configure_autotools(self):
- if not self._autotools:
- self._autotools = AutoToolsBuildEnvironment(self)
- args=[]
+ def _configure_meson(self):
+ if not self._meson:
+ self._meson = Meson(self)
+ defs={}
# FIXME: add dbus when #2147 is merged
- for lib in ["alsa", "x11", "openssl"]:
- args.append("--%s-%s" % ("enable" if getattr(self.options, "with_" + lib) else "disable", lib))
- args.append("--%s-glib2" % ("enable" if self.options.with_glib else "disable"))
- args.append("--%s-fftw" % ("with" if self.options.with_fftw else "without"))
- if self.options.shared:
- args.extend(["--enable-shared=yes", "--enable-static=no"])
- else:
- args.extend(["--enable-shared=no", "--enable-static=yes"])
- args.append("--with-udev-rules-dir=%s" % os.path.join(self.package_folder, "bin", "udev", "rules.d"))
- args.append("--with-systemduserunitdir=%s" % os.path.join(self.build_folder, "ignore"))
+ for lib in ["alsa", "x11", "openssl", "glib2", "fftw"]:
+ defs[lib] = ("enabled" if self.options.get_safe("with_" + lib) else "disabled")
+ defs["udevrulesdir"] = os.path.join(self.package_folder, "bin", "udev", "rules.d")
+ defs["systemduserunitdir"] = os.path.join(self.build_folder, "ignore")
+ defs["database"] = self.options.database
+ defs["bluez5"] = self.options.bluez5
+ defs["tests"] = False
+
with tools.environment_append({"PKG_CONFIG_PATH": self.build_folder}):
with tools.environment_append({
"FFTW_CFLAGS": tools.PkgConfig("fftw").cflags,
"FFTW_LIBS": tools.PkgConfig("fftw").libs}) if self.options.with_fftw else tools.no_op():
with tools.environment_append(RunEnvironment(self).vars):
- self._autotools.configure(args=args, configure_dir=self._source_subfolder)
- return self._autotools
+ self._meson.configure(defs=defs, source_folder=self._source_subfolder, build_folder = self._build_subfolder)
+ return self._meson
def build(self):
for patch in self.conan_data.get("patches", {}).get(self.version, []):
tools.patch(**patch)
- autotools = self._configure_autotools()
- autotools.make()
+ tools.replace_in_file(os.path.join(self._source_subfolder, "meson.build"),
+ "bash_completion_dep.found()",
+ "false")
+ meson = self._configure_meson()
+ meson.build()
def package(self):
self.copy(pattern="LICENSE", dst="licenses", src=self._source_subfolder)
- autotools = self._configure_autotools()
- autotools.install()
+ meson = self._configure_meson()
+ meson.install()
tools.rmdir(os.path.join(self.package_folder, "etc"))
tools.rmdir(os.path.join(self.package_folder, "share"))
tools.rmdir(os.path.join(self.package_folder, "lib", "cmake"))
From 04426260889d7a7e3186875d91789ae6a91db964 Mon Sep 17 00:00:00 2001
From: ericLemanissier
Date: Sun, 10 Jan 2021 20:47:33 +0100
Subject: [PATCH 6/6] disable b_lundef on FreeBSD
---
recipes/pulseaudio/all/conanfile.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/recipes/pulseaudio/all/conanfile.py b/recipes/pulseaudio/all/conanfile.py
index 6e836372721dc..cfcf31a6c9bce 100644
--- a/recipes/pulseaudio/all/conanfile.py
+++ b/recipes/pulseaudio/all/conanfile.py
@@ -109,6 +109,8 @@ def _configure_meson(self):
defs["database"] = self.options.database
defs["bluez5"] = self.options.bluez5
defs["tests"] = False
+ if self.settings.os == "FreeBSD":
+ defs["b_lundef"] = False
with tools.environment_append({"PKG_CONFIG_PATH": self.build_folder}):
with tools.environment_append({