diff --git a/NEWS b/NEWS
index a9a846f70..74e975f31 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ ver 0.22.7 (not yet released)
- simple: fix database corruption bug
* output
- fix crash when pausing with multiple partitions
+ - jack: enable on Windows
- httpd: send header "Access-Control-Allow-Origin: *"
- wasapi: add algorithm for finding usable audio format
- wasapi: use default device only if none was configured
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 71b9ad004..3a4cf4b1a 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -910,6 +910,10 @@ jack
The jack plugin connects to a `JACK server `_.
+On Windows, this plugin loads :file:`libjack64.dll` at runtime. This
+means you need to `download and install the JACK windows build
+`_.
+
.. list-table::
:widths: 20 80
:header-rows: 1
diff --git a/python/build/jack.py b/python/build/jack.py
new file mode 100644
index 000000000..6e71e7f1d
--- /dev/null
+++ b/python/build/jack.py
@@ -0,0 +1,47 @@
+import os, shutil
+import re
+
+from .project import Project
+
+# This class installs just the public headers and a fake pkg-config
+# file which defines the macro "DYNAMIC_JACK". This tells MPD's JACK
+# output plugin to load the libjack64.dll dynamically using
+# LoadLibrary(). This kludge avoids the runtime DLL dependency for
+# users who don't use JACK, but still allows using the system JACK
+# client library.
+#
+# The problem with JACK is that it uses an extremely fragile shared
+# memory protocol to communicate with the daemon. One needs to use
+# daemon and client library from the same build. That's why we don't
+# build libjack statically here; it would probably not be compatible
+# with the user's JACK daemon.
+
+class JackProject(Project):
+ def __init__(self, url, md5, installed,
+ **kwargs):
+ m = re.match(r'.*/v([\d.]+)\.tar\.gz$', url)
+ self.version = m.group(1)
+ Project.__init__(self, url, md5, installed,
+ name='jack2', version=self.version,
+ base='jack2-' + self.version,
+ **kwargs)
+
+ def build(self, toolchain):
+ src = self.unpack(toolchain)
+
+ includes = ['jack.h', 'ringbuffer.h', 'systemdeps.h', 'transport.h', 'types.h', 'weakmacros.h']
+ includedir = os.path.join(toolchain.install_prefix, 'include', 'jack')
+ os.makedirs(includedir, exist_ok=True)
+
+ for i in includes:
+ shutil.copyfile(os.path.join(src, 'common', 'jack', i),
+ os.path.join(includedir, i))
+
+ with open(os.path.join(toolchain.install_prefix, 'lib', 'pkgconfig', 'jack.pc'), 'w') as f:
+ print("prefix=" + toolchain.install_prefix, file=f)
+ print("", file=f)
+ print("Name: jack", file=f)
+ print("Description: dummy", file=f)
+ print("Version: " + self.version, file=f)
+ print("Libs: ", file=f)
+ print("Cflags: -DDYNAMIC_JACK", file=f)
diff --git a/python/build/libs.py b/python/build/libs.py
index e5277856c..aa64db05c 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -9,6 +9,7 @@ from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject
from build.openssl import OpenSSLProject
from build.boost import BoostProject
+from build.jack import JackProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
@@ -443,6 +444,12 @@ libnfs = AutotoolsProject(
autoreconf=True,
)
+jack = JackProject(
+ 'https://github.com/jackaudio/jack2/archive/v1.9.17.tar.gz',
+ '38f674bbc57852a8eb3d9faa1f96a0912d26f7d5df14c11005ad499c8ae352f2',
+ 'lib/pkgconfig/jack.pc',
+)
+
boost = BoostProject(
'https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.bz2',
'953db31e016db7bb207f11432bef7df100516eeb746843fa0486a222e3fd49cb',
diff --git a/src/lib/jack/Dynamic.hxx b/src/lib/jack/Dynamic.hxx
new file mode 100644
index 000000000..92d4d72c9
--- /dev/null
+++ b/src/lib/jack/Dynamic.hxx
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2003-2021 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "system/Error.hxx"
+
+/* sorry for this horrible piece of code - there's no elegant way to
+ load DLLs at runtime */
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+
+using jack_set_error_function_t = std::add_pointer_t;
+static jack_set_error_function_t _jack_set_error_function;
+
+using jack_set_info_function_t = std::add_pointer_t;
+static jack_set_info_function_t _jack_set_info_function;
+
+using jack_client_open_t = std::add_pointer_t;
+static jack_client_open_t _jack_client_open;
+
+using jack_client_close_t = std::add_pointer_t;
+static jack_client_close_t _jack_client_close;
+
+using jack_connect_t = std::add_pointer_t;
+static jack_connect_t _jack_connect;
+
+using jack_activate_t = std::add_pointer_t;
+static jack_activate_t _jack_activate;
+
+using jack_deactivate_t = std::add_pointer_t;
+static jack_deactivate_t _jack_deactivate;
+
+using jack_get_sample_rate_t = std::add_pointer_t;
+static jack_get_sample_rate_t _jack_get_sample_rate;
+
+using jack_set_process_callback_t = std::add_pointer_t;
+static jack_set_process_callback_t _jack_set_process_callback;
+
+using jack_on_info_shutdown_t = std::add_pointer_t;
+static jack_on_info_shutdown_t _jack_on_info_shutdown;
+
+using jack_free_t = std::add_pointer_t;
+static jack_free_t _jack_free;
+
+using jack_get_ports_t = std::add_pointer_t;
+static jack_get_ports_t _jack_get_ports;
+
+using jack_port_register_t = std::add_pointer_t;
+static jack_port_register_t _jack_port_register;
+
+using jack_port_name_t = std::add_pointer_t;
+static jack_port_name_t _jack_port_name;
+
+using jack_port_get_buffer_t = std::add_pointer_t;
+static jack_port_get_buffer_t _jack_port_get_buffer;
+
+using jack_ringbuffer_create_t = std::add_pointer_t;
+static jack_ringbuffer_create_t _jack_ringbuffer_create;
+
+using jack_ringbuffer_free_t = std::add_pointer_t;
+static jack_ringbuffer_free_t _jack_ringbuffer_free;
+
+using jack_ringbuffer_get_write_vector_t = std::add_pointer_t;
+static jack_ringbuffer_get_write_vector_t _jack_ringbuffer_get_write_vector;
+
+using jack_ringbuffer_write_advance_t = std::add_pointer_t;
+static jack_ringbuffer_write_advance_t _jack_ringbuffer_write_advance;
+
+using jack_ringbuffer_read_space_t = std::add_pointer_t;
+static jack_ringbuffer_read_space_t _jack_ringbuffer_read_space;
+
+using jack_ringbuffer_read_t = std::add_pointer_t;
+static jack_ringbuffer_read_t _jack_ringbuffer_read;
+
+using jack_ringbuffer_read_advance_t = std::add_pointer_t;
+static jack_ringbuffer_read_advance_t _jack_ringbuffer_read_advance;
+
+using jack_ringbuffer_reset_t = std::add_pointer_t;
+static jack_ringbuffer_reset_t _jack_ringbuffer_reset;
+
+template
+static void
+GetFunction(HMODULE h, const char *name, T &result)
+{
+ auto f = GetProcAddress(h, name);
+ if (f == nullptr)
+ throw FormatRuntimeError("No such libjack function: %s", name);
+
+ result = reinterpret_cast(f);
+}
+
+static void
+LoadJackLibrary()
+{
+#ifdef _WIN64
+#define LIBJACK "libjack64"
+#else
+#define LIBJACK "libjack"
+#endif
+
+ auto libjack = LoadLibraryA(LIBJACK);
+ if (!libjack)
+ throw FormatLastError("Failed to load " LIBJACK ".dll");
+
+ GetFunction(libjack, "jack_set_error_function", _jack_set_error_function);
+ GetFunction(libjack, "jack_set_info_function", _jack_set_info_function);
+
+ GetFunction(libjack, "jack_client_open", _jack_client_open);
+ GetFunction(libjack, "jack_client_close", _jack_client_close);
+ GetFunction(libjack, "jack_connect", _jack_connect);
+ GetFunction(libjack, "jack_activate", _jack_activate);
+ GetFunction(libjack, "jack_deactivate", _jack_deactivate);
+ GetFunction(libjack, "jack_free", _jack_free);
+
+ GetFunction(libjack, "jack_get_sample_rate", _jack_get_sample_rate);
+ GetFunction(libjack, "jack_set_process_callback", _jack_set_process_callback);
+ GetFunction(libjack, "jack_on_info_shutdown", _jack_on_info_shutdown);
+
+ GetFunction(libjack, "jack_get_ports", _jack_get_ports);
+ GetFunction(libjack, "jack_port_register", _jack_port_register);
+ GetFunction(libjack, "jack_port_name", _jack_port_name);
+ GetFunction(libjack, "jack_port_get_buffer", _jack_port_get_buffer);
+
+ GetFunction(libjack, "jack_ringbuffer_create", _jack_ringbuffer_create);
+ GetFunction(libjack, "jack_ringbuffer_free", _jack_ringbuffer_free);
+ GetFunction(libjack, "jack_ringbuffer_get_write_vector", _jack_ringbuffer_get_write_vector);
+ GetFunction(libjack, "jack_ringbuffer_write_advance", _jack_ringbuffer_write_advance);
+ GetFunction(libjack, "jack_ringbuffer_read_space", _jack_ringbuffer_read_space);
+ GetFunction(libjack, "jack_ringbuffer_read", _jack_ringbuffer_read);
+ GetFunction(libjack, "jack_ringbuffer_read_advance", _jack_ringbuffer_read_advance);
+ GetFunction(libjack, "jack_ringbuffer_reset", _jack_ringbuffer_reset);
+}
+
+#define jack_set_error_function _jack_set_error_function
+#define jack_set_info_function _jack_set_info_function
+
+#define jack_client_open _jack_client_open
+#define jack_client_close _jack_client_close
+#define jack_connect _jack_connect
+#define jack_activate _jack_activate
+#define jack_deactivate _jack_deactivate
+#define jack_free _jack_free
+
+#define jack_get_sample_rate _jack_get_sample_rate
+#define jack_set_process_callback _jack_set_process_callback
+#define jack_on_info_shutdown _jack_on_info_shutdown
+
+#define jack_get_ports _jack_get_ports
+#define jack_port_register _jack_port_register
+#define jack_port_name _jack_port_name
+#define jack_port_get_buffer _jack_port_get_buffer
+
+#define jack_ringbuffer_create _jack_ringbuffer_create
+#define jack_ringbuffer_free _jack_ringbuffer_free
+#define jack_ringbuffer_get_write_vector _jack_ringbuffer_get_write_vector
+#define jack_ringbuffer_write_advance _jack_ringbuffer_write_advance
+#define jack_ringbuffer_read_space _jack_ringbuffer_read_space
+#define jack_ringbuffer_read _jack_ringbuffer_read
+#define jack_ringbuffer_read_advance _jack_ringbuffer_read_advance
+#define jack_ringbuffer_reset _jack_ringbuffer_reset
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index 5f58f93f1..b0cb910d6 100644
--- a/src/output/plugins/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -44,6 +44,10 @@ static constexpr unsigned MAX_PORTS = 16;
static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+#ifdef DYNAMIC_JACK
+#include "lib/jack/Dynamic.hxx"
+#endif // _WIN32
+
class JackOutput final : public AudioOutput {
/**
* libjack options passed to jack_client_open().
@@ -463,6 +467,10 @@ JackOutput::Disable() noexcept
static AudioOutput *
mpd_jack_init(EventLoop &, const ConfigBlock &block)
{
+#ifdef DYNAMIC_JACK
+ LoadJackLibrary();
+#endif
+
jack_set_error_function(mpd_jack_error);
#ifdef HAVE_JACK_SET_INFO_FUNCTION
diff --git a/win32/build.py b/win32/build.py
index 8aa87e288..9f3011831 100755
--- a/win32/build.py
+++ b/win32/build.py
@@ -108,6 +108,7 @@ thirdparty_libs = [
curl,
libexpat,
libnfs,
+ jack,
boost,
]