diff --git a/libs/ardour/ardour/control_protocol_manager.h b/libs/ardour/ardour/control_protocol_manager.h index 0c82d1d6c0..4b0a688653 100644 --- a/libs/ardour/ardour/control_protocol_manager.h +++ b/libs/ardour/ardour/control_protocol_manager.h @@ -75,6 +75,7 @@ class LIBARDOUR_API ControlProtocolManager : public PBD::Stateful, public ARDOUR void midi_connectivity_established (); void drop_protocols (); void probe_midi_control_protocols (); + void probe_usb_control_protocols (bool, uint16_t, uint16_t); int activate (ControlProtocolInfo&); int deactivate (ControlProtocolInfo&); diff --git a/libs/ardour/control_protocol_manager.cc b/libs/ardour/control_protocol_manager.cc index c93405f6ab..4e9619b897 100644 --- a/libs/ardour/control_protocol_manager.cc +++ b/libs/ardour/control_protocol_manager.cc @@ -21,6 +21,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifdef HAVE_USB +#include +/* ControlProtocolManager is a singleton, so we can use static + * here. This has the advantage that libusb.h does not need + * to be used in ardour/control_protocol_manager.h which + * is included by various UIs + */ +static libusb_hotplug_callback_handle _hpcp = 0; +static libusb_context* _usb_ctx = NULL; +static pthread_t _hotplug_thread; +static bool _hotplug_thread_run = false; +#endif + #include #include @@ -29,6 +42,7 @@ #include "pbd/event_loop.h" #include "pbd/file_utils.h" #include "pbd/error.h" +#include "pbd/stacktrace.h" #include "control_protocol/control_protocol.h" @@ -49,6 +63,33 @@ ControlProtocolManager* ControlProtocolManager::_instance = 0; const string ControlProtocolManager::state_node_name = X_("ControlProtocols"); PBD::Signal1 ControlProtocolManager::StripableSelectionChanged; +#ifdef HAVE_USB +static int +usb_hotplug_cb (libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* user_data) +{ + ControlProtocolManager* cpm = static_cast (user_data); + struct libusb_device_descriptor desc; + if (LIBUSB_SUCCESS == libusb_get_device_descriptor (device, &desc)) { + DEBUG_TRACE (DEBUG::ControlProtocols, string_compose ("USB Hotplug: %1 vendor: %2 product: %3\n", + (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) ? "arrived" : "removed", std::hex, desc.idVendor, desc.idProduct)); + cpm->probe_usb_control_protocols (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, desc.idVendor, desc.idProduct); + } + + return _hotplug_thread_run ? 0 : 1; +} + +static void* +usb_hotplug_thread (void* user_data) +{ + while (_hotplug_thread_run) { + if (libusb_handle_events (_usb_ctx) < 0) { + break; + } + } + return 0; +} +#endif + ControlProtocolInfo::~ControlProtocolInfo () { if (protocol && descriptor) { @@ -62,6 +103,9 @@ ControlProtocolInfo::~ControlProtocolInfo () delete (Glib::Module*) descriptor->module; descriptor = 0; } +#ifdef HAVE_USB + assert (!_hotplug_thread_run); +#endif } ControlProtocolManager::ControlProtocolManager () @@ -93,6 +137,17 @@ ControlProtocolManager::set_session (Session* s) SessionHandlePtr::set_session (s); if (!_session) { +#ifdef HAVE_USB + if (_hotplug_thread_run) { + _hotplug_thread_run = false; + libusb_hotplug_deregister_callback (_usb_ctx, _hpcp); + pthread_join (_hotplug_thread, NULL); + } + if (_usb_ctx) { + libusb_exit (_usb_ctx); + _usb_ctx = NULL; + } +#endif return; } @@ -116,6 +171,24 @@ ControlProtocolManager::set_session (Session* s) StripableSelectionChanged (v); /* EMIT SIGNAL */ } } + +#ifdef HAVE_USB + if (LIBUSB_SUCCESS == libusb_init (&_usb_ctx) && libusb_has_capability (LIBUSB_CAP_HAS_HOTPLUG)) { + if (LIBUSB_SUCCESS == libusb_hotplug_register_callback (_usb_ctx, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_ENUMERATE, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + usb_hotplug_cb, this, + &_hpcp)) { + _hotplug_thread_run = true; + if (pthread_create (&_hotplug_thread, NULL, usb_hotplug_thread, this)) { + _hotplug_thread_run = false; + } + } + } +#endif } int @@ -586,6 +659,38 @@ ControlProtocolManager::probe_midi_control_protocols () } } +void +ControlProtocolManager::probe_usb_control_protocols (bool arrived, uint16_t vendor, uint16_t product) +{ + if (!Config->get_auto_enable_surfaces ()) { + return; + } + for (auto const& cpi : control_protocol_info) { + /* Note: manual teardown deletes the descriptor */ + if (!cpi->descriptor) { + cpi->automatic = false; + continue; + } + if (!cpi->descriptor->match_usb || !cpi->descriptor->match_usb (vendor, product)) { + continue; + } + + bool active = 0 != cpi->protocol; + + if (!active && arrived) { + cpi->automatic = true; + activate (*cpi); + } else if (active && cpi->automatic && !arrived) { + cpi->automatic = false; + deactivate (*cpi); + /* allow to auto-enable again */ + if (!cpi->descriptor) { + cpi->descriptor = get_descriptor (cpi->path); + } + } + } +} + void ControlProtocolManager::stripable_selection_changed (StripableNotificationListPtr sp) { diff --git a/libs/ardour/wscript b/libs/ardour/wscript index f5a8af4118..fd491678a1 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -391,7 +391,7 @@ def build(bld): obj.includes = ['.', '../vst3/', '../ctrl-interface/control_protocol', '..'] obj.name = 'libardour' obj.target = 'ardour' - obj.uselib = ['GLIBMM','GTHREAD','AUBIO','SIGCPP','XML','UUID', 'LO', + obj.uselib = ['GLIBMM','GTHREAD','AUBIO','SIGCPP','XML','UUID', 'LO', 'USB', 'SNDFILE','SAMPLERATE','LRDF','AUDIOUNITS', 'GIOMM', 'FFTW3F', 'OSX','BOOST','CURL','TAGLIB','VAMPSDK','VAMPHOSTSDK','RUBBERBAND'] obj.use = ['libpbd','libmidipp','libevoral', @@ -591,7 +591,7 @@ def build(bld): testcommon.includes = obj.includes + ['test', '../pbd', '..'] testcommon.source = ['test/testrunner.cc', 'test/test_needing_session.cc', 'test/dummy_lxvst.cc', 'test/audio_region_test.cc', 'test/test_util.cc', 'test/test_ui.cc'] - testcommon.uselib = ['CPPUNIT','SIGCPP','GLIBMM','GTHREAD', 'OSX', 'FFTW3F', + testcommon.uselib = ['CPPUNIT','SIGCPP','GLIBMM','GTHREAD', 'OSX', 'FFTW3F', 'USB', 'SAMPLERATE','XML','LRDF','COREAUDIO','TAGLIB','VAMPSDK','VAMPHOSTSDK','RUBBERBAND'] testcommon.use = ['libpbd', 'libmidipp', 'libevoral', 'libaudiographer', 'libardour'] if bld.is_defined('USE_EXTERNAL_LIBS'): @@ -711,7 +711,7 @@ def create_ardour_test_program(bld, includes, name, target, sources): testobj = bld(features = 'cxx cxxprogram') testobj.includes = includes + ['test', '../pbd', '..'] testobj.source = sources - testobj.uselib = ['CPPUNIT','SIGCPP','GLIBMM','GTHREAD', 'FFTW3F', 'OSX', + testobj.uselib = ['CPPUNIT','SIGCPP','GLIBMM','GTHREAD', 'FFTW3F', 'OSX', 'USB', 'SAMPLERATE','XML','LRDF','COREAUDIO','TAGLIB','VAMPSDK','VAMPHOSTSDK','RUBBERBAND'] testobj.use = [ 'testcommon' ] testobj.name = name diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript index 49d74c7af8..47f05ee7de 100644 --- a/libs/surfaces/wscript +++ b/libs/surfaces/wscript @@ -42,7 +42,6 @@ def configure(conf): autowaf.set_recursive() - autowaf.check_pkg(conf, 'libusb-1.0', uselib_store='USB', mandatory=False) #if Options.options.tranzport and conf.is_defined('HAVE_USB'): # conf.define('BUILD_TRANZPORT', 1) diff --git a/wscript b/wscript index c0a8c80dc3..7f11626aa8 100644 --- a/wscript +++ b/wscript @@ -1201,6 +1201,8 @@ def configure(conf): autowaf.check_pkg(conf, 'vamp-hostsdk', uselib_store='VAMPHOSTSDK', atleast_version='2.1', mandatory=True) autowaf.check_pkg(conf, 'rubberband', uselib_store='RUBBERBAND', mandatory=True) + autowaf.check_pkg(conf, 'libusb-1.0', uselib_store='USB', atleast_version='1.0.16', mandatory=False) + # we cannot rely on pkg-config - https://lists.linuxaudio.org/archives/linux-audio-dev/2022-July/038395.html conf.check_cc( features = 'cxx',