// Copyright 2007-2022 David Robillard // SPDX-License-Identifier: ISC #include "dylib.h" #include "suil_internal.h" #include "lv2/core/lv2.h" #include "lv2/ui/ui.h" #include "suil/suil.h" #include #include #include #include #include #define GTK2_UI_URI LV2_UI__GtkUI #define GTK3_UI_URI LV2_UI__Gtk3UI #define QT5_UI_URI LV2_UI__Qt5UI #define X11_UI_URI LV2_UI__X11UI #define WIN_UI_URI LV2_UI_PREFIX "WindowsUI" #define COCOA_UI_URI LV2_UI__CocoaUI SUIL_API unsigned suil_ui_supported(const char* host_type_uri, const char* ui_type_uri) { enum { SUIL_WRAPPING_UNSUPPORTED = 0, SUIL_WRAPPING_NATIVE = 1, SUIL_WRAPPING_EMBEDDED = 2 }; if (!strcmp(host_type_uri, ui_type_uri)) { return SUIL_WRAPPING_NATIVE; } if ((!strcmp(host_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, QT5_UI_URI)) || (!strcmp(host_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, GTK2_UI_URI)) || (!strcmp(host_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) || (!strcmp(host_type_uri, GTK3_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) || (!strcmp(host_type_uri, GTK3_UI_URI) && !strcmp(ui_type_uri, QT5_UI_URI)) || (!strcmp(host_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, WIN_UI_URI)) || (!strcmp(host_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, COCOA_UI_URI)) || (!strcmp(host_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) || (!strcmp(host_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, COCOA_UI_URI))) { return SUIL_WRAPPING_EMBEDDED; } return SUIL_WRAPPING_UNSUPPORTED; } static SuilWrapper* open_wrapper(SuilHost* host, const char* container_type_uri, const char* ui_type_uri, LV2_Feature*** features, unsigned n_features) { const char* module_name = NULL; if (!strcmp(container_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, GTK2_UI_URI)) { module_name = "suil_gtk2_in_qt5"; } if (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, QT5_UI_URI)) { module_name = "suil_qt5_in_gtk2"; } if (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) { module_name = "suil_x11_in_gtk2"; } if (!strcmp(container_type_uri, GTK3_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) { module_name = "suil_x11_in_gtk3"; } if (!strcmp(container_type_uri, GTK3_UI_URI) && !strcmp(ui_type_uri, QT5_UI_URI)) { module_name = "suil_qt5_in_gtk3"; } if (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, WIN_UI_URI)) { module_name = "suil_win_in_gtk2"; } if (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, COCOA_UI_URI)) { module_name = "suil_cocoa_in_gtk2"; } if (!strcmp(container_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) { module_name = "suil_x11_in_qt5"; } if (!strcmp(container_type_uri, QT5_UI_URI) && !strcmp(ui_type_uri, COCOA_UI_URI)) { module_name = "suil_cocoa_in_qt5"; } if (!module_name) { SUIL_ERRORF("Unable to wrap UI type <%s> as type <%s>\n", ui_type_uri, container_type_uri); return NULL; } void* const lib = suil_open_module(module_name); if (!lib) { return NULL; } SuilWrapperNewFunc wrapper_new = (SuilWrapperNewFunc)suil_dlfunc(lib, "suil_wrapper_new"); SuilWrapper* wrapper = wrapper_new ? wrapper_new(host, container_type_uri, ui_type_uri, features, n_features) : NULL; if (wrapper) { wrapper->lib = lib; } else { SUIL_ERRORF("Corrupt wrap module %s\n", module_name); dylib_close(lib); } return wrapper; } SUIL_API SuilInstance* suil_instance_new(SuilHost* host, SuilController controller, const char* container_type_uri, const char* plugin_uri, const char* ui_uri, const char* ui_type_uri, const char* ui_bundle_path, const char* ui_binary_path, const LV2_Feature* const* features) { // Open UI library dylib_error(); void* lib = dylib_open(ui_binary_path, DYLIB_NOW); if (!lib) { SUIL_ERRORF( "Unable to open UI library %s (%s)\n", ui_binary_path, dylib_error()); return NULL; } // Get discovery function LV2UI_DescriptorFunction df = (LV2UI_DescriptorFunction)suil_dlfunc(lib, "lv2ui_descriptor"); if (!df) { SUIL_ERRORF("Broken LV2 UI %s (no lv2ui_descriptor symbol found)\n", ui_binary_path); dylib_close(lib); return NULL; } // Get UI descriptor const LV2UI_Descriptor* descriptor = NULL; for (uint32_t i = 0; true; ++i) { const LV2UI_Descriptor* ld = df(i); if (!ld) { break; } if (!strcmp(ld->URI, ui_uri)) { descriptor = ld; break; } } if (!descriptor) { SUIL_ERRORF( "Failed to find descriptor for <%s> in %s\n", ui_uri, ui_binary_path); dylib_close(lib); return NULL; } // Create SuilInstance SuilInstance* instance = (SuilInstance*)calloc(1, sizeof(SuilInstance)); if (!instance) { SUIL_ERRORF("Failed to allocate memory for <%s> instance\n", ui_uri); dylib_close(lib); return NULL; } instance->lib_handle = lib; instance->descriptor = descriptor; // Make UI features array instance->features = (LV2_Feature**)malloc(sizeof(LV2_Feature*)); instance->features[0] = NULL; // Copy user provided features const LV2_Feature* const* fi = features; unsigned n_features = 0; while (fi && *fi) { const LV2_Feature* f = *fi++; suil_add_feature(&instance->features, &n_features, f->URI, f->data); } // Add additional features implemented by SuilHost functions if (host->index_func) { instance->port_map.handle = controller; instance->port_map.port_index = host->index_func; suil_add_feature( &instance->features, &n_features, LV2_UI__portMap, &instance->port_map); } if (host->subscribe_func && host->unsubscribe_func) { instance->port_subscribe.handle = controller; instance->port_subscribe.subscribe = host->subscribe_func; instance->port_subscribe.unsubscribe = host->unsubscribe_func; suil_add_feature(&instance->features, &n_features, LV2_UI__portSubscribe, &instance->port_subscribe); } if (host->touch_func) { instance->touch.handle = controller; instance->touch.touch = host->touch_func; suil_add_feature( &instance->features, &n_features, LV2_UI__touch, &instance->touch); } // Open wrapper (this may add additional features) if (container_type_uri && strcmp(container_type_uri, ui_type_uri)) { instance->wrapper = open_wrapper( host, container_type_uri, ui_type_uri, &instance->features, n_features); if (!instance->wrapper) { suil_instance_free(instance); return NULL; } } // Instantiate UI instance->handle = descriptor->instantiate(descriptor, plugin_uri, ui_bundle_path, host->write_func, controller, &instance->ui_widget, (const LV2_Feature* const*)instance->features); // Failed to instantiate UI if (!instance->handle) { SUIL_ERRORF( "Failed to instantiate UI <%s> in %s\n", ui_uri, ui_binary_path); suil_instance_free(instance); return NULL; } if (instance->wrapper) { if (instance->wrapper->wrap(instance->wrapper, instance)) { SUIL_ERRORF( "Failed to wrap UI <%s> in type <%s>\n", ui_uri, container_type_uri); suil_instance_free(instance); return NULL; } } else { instance->host_widget = instance->ui_widget; } return instance; } SUIL_API void suil_instance_free(SuilInstance* instance) { if (instance) { for (unsigned i = 0; instance->features[i]; ++i) { free(instance->features[i]); } free(instance->features); // Call wrapper free function to destroy widgets and drop references if (instance->wrapper && instance->wrapper->free) { instance->wrapper->free(instance->wrapper); } // Call cleanup to destroy UI (if it still exists at this point) if (instance->handle) { instance->descriptor->cleanup(instance->handle); } dylib_close(instance->lib_handle); // Close libraries and free everything if (instance->wrapper) { #ifndef _WIN32 // Never unload modules on windows, causes mysterious segfaults dylib_close(instance->wrapper->lib); #endif free(instance->wrapper); } free(instance); } } SUIL_API SuilHandle suil_instance_get_handle(SuilInstance* instance) { return instance->handle; } SUIL_API LV2UI_Widget suil_instance_get_widget(SuilInstance* instance) { return instance->host_widget; } SUIL_API void suil_instance_port_event(SuilInstance* instance, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer) { if (instance->descriptor->port_event) { instance->descriptor->port_event( instance->handle, port_index, buffer_size, format, buffer); } } SUIL_API const void* suil_instance_extension_data(SuilInstance* instance, const char* uri) { if (instance->descriptor->extension_data) { return instance->descriptor->extension_data(uri); } return NULL; }