diff options
-rw-r--r-- | src/gtk2_in_qt4.cpp | 77 | ||||
-rw-r--r-- | src/instance.c | 111 | ||||
-rw-r--r-- | src/qt4_in_gtk2.cpp | 146 | ||||
-rw-r--r-- | src/suil_internal.h | 17 | ||||
-rw-r--r-- | wscript | 30 |
5 files changed, 364 insertions, 17 deletions
diff --git a/src/gtk2_in_qt4.cpp b/src/gtk2_in_qt4.cpp new file mode 100644 index 0000000..837cbac --- /dev/null +++ b/src/gtk2_in_qt4.cpp @@ -0,0 +1,77 @@ +/* Suil, an LV2 plugin UI hosting library. + * Copyright 2011 David Robillard <d@drobilla.net> + * + * Suil 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 3 of the License, or + * (at your option) any later version. + * + * Suil 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 details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QX11EmbedContainer> +#undef signals + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "suil_internal.h" + +extern "C" { + +SUIL_API +int +suil_wrap_init(const char* host_type_uri, + const char* ui_type_uri, + const LV2_Feature* const* features) +{ + return 0; +} + +static void +on_window_destroy(GtkWidget* gtk_window, + gpointer* user_data) +{ + //SuilInstance instance = (SuilInstance)user_data; + // TODO: Notify host via callback + printf("LV2 UI window destroyed\n"); +} + + +/** Dynamic module entry point. */ +SUIL_API +int +suil_instance_wrap(SuilInstance instance, + const char* host_type_uri, + const char* ui_type_uri) +{ + GtkWidget* gtk_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_resizable(GTK_WINDOW(gtk_window), TRUE); + gtk_window_set_title(GTK_WINDOW(gtk_window), "LV2 Plugin UI"); + + gtk_container_add(GTK_CONTAINER(gtk_window), + (GtkWidget*)instance->ui_widget); + + g_signal_connect(G_OBJECT(gtk_window), + "destroy", + G_CALLBACK(on_window_destroy), + instance); + + gtk_widget_show_all(gtk_window); + + GdkWindow* gdk_window = gtk_widget_get_window(gtk_window); + QX11EmbedContainer* wrapper = new QX11EmbedContainer(); + wrapper->embedClient(GDK_WINDOW_XID(gdk_window)); + + instance->host_widget = wrapper; + + return 0; +} + +} // extern "C" diff --git a/src/instance.c b/src/instance.c index 74d0948..a56a11f 100644 --- a/src/instance.c +++ b/src/instance.c @@ -25,14 +25,78 @@ #include <dlfcn.h> +#include "suil-config.h" #include "suil_internal.h" +#define NS_UI "http://lv2plug.in/ns/extensions/ui#" +#define GTK2_UI_URI NS_UI "GtkUI" +#define QT4_UI_URI NS_UI "Qt4UI" + SUIL_API bool suil_ui_type_supported(const char* host_type_uri, const char* ui_type_uri) { - return !strcmp(ui_type_uri, "http://lv2plug.in/ns/extensions/ui#GtkUI"); + return (!strcmp(host_type_uri, GTK2_UI_URI) + || !strcmp(host_type_uri, QT4_UI_URI)) + && (!strcmp(ui_type_uri, GTK2_UI_URI) + || !strcmp(ui_type_uri, QT4_UI_URI)); +} + +struct _SuilModule { + SuilWrapInitFunc init; + SuilWrapFunc wrap; +}; + +typedef struct _SuilModule* SuilModule; + +static SuilModule +get_wrap_module(const char* host_type_uri, + const char* ui_type_uri) +{ + if (!strcmp(host_type_uri, ui_type_uri)) { + return NULL; + } + + const char* module_name = NULL; + if (!strcmp(host_type_uri, QT4_UI_URI) + && !strcmp(ui_type_uri, GTK2_UI_URI)) { + module_name = "libsuil_gtk2_in_qt4"; + } else if (!strcmp(host_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, QT4_UI_URI)) { + module_name = "libsuil_qt4_in_gtk2"; + } + + if (!module_name) { + SUIL_ERRORF("Unable to wrap UI type <%s> as type <%s>\n", + ui_type_uri, host_type_uri); + return NULL; + } + + const size_t path_len = strlen(SUIL_MODULE_DIR) + + strlen(module_name) + + strlen(SUIL_MODULE_EXT) + + 2; + + char* const path = calloc(path_len, 1); + snprintf(path, path_len, "%s%s%s%s", + SUIL_MODULE_DIR, SUIL_DIR_SEP, module_name, SUIL_MODULE_EXT); + + // Open wrap module + dlerror(); + void* lib = dlopen(path, RTLD_NOW); + if (!lib) { + SUIL_ERRORF("Unable to open wrap module %s\n", path); + return NULL; + } + + SuilModule module = (SuilModule)malloc(sizeof(struct _SuilModule)); + module->init = (SuilWrapInitFunc)suil_dlfunc(lib, "suil_wrap_init"); + module->wrap = (SuilWrapFunc)suil_dlfunc(lib, "suil_wrap"); + + free(path); + + return module; } SUIL_API @@ -92,30 +156,33 @@ suil_instance_new(SuilUIs uis, return NULL; } - // Create empty local features array if necessary - const bool local_features = (features == NULL); - if (local_features) { - features = malloc(sizeof(LV2_Feature)); - ((LV2_Feature**)features)[0] = NULL; + // Use empty local features array if necessary + const LV2_Feature* local_features[1]; + local_features[0] = NULL; + if (!features) { + features = (const LV2_Feature* const*)&local_features; + } + + SuilModule module = get_wrap_module(type_uri, ui->type_uri); + if (module) { + module->init(type_uri, ui->type_uri, features); } // Instantiate UI struct _SuilInstance* instance = malloc(sizeof(struct _SuilInstance)); - instance->lib_handle = lib; - instance->descriptor = descriptor; - instance->handle = descriptor->instantiate( + instance->lib_handle = lib; + instance->descriptor = descriptor; + instance->host_widget = NULL; + instance->ui_widget = NULL; + instance->handle = descriptor->instantiate( descriptor, uis->plugin_uri, ui->bundle_path, write_function, controller, - &instance->widget, + &instance->ui_widget, features); - if (local_features) { - free((LV2_Feature**)features); - } - // Failed to find or instantiate UI if (!instance || !instance->handle) { SUIL_ERRORF("Failed to instantiate UI <%s> in %s\n", @@ -126,13 +193,25 @@ suil_instance_new(SuilUIs uis, } // Got a handle, but failed to create a widget (buggy UI) - if (!instance->widget) { + if (!instance->ui_widget) { SUIL_ERRORF("Widget creation failed for UI <%s> in %s\n", ui->uri, ui->binary_path); suil_instance_free(instance); return NULL; } + if (module) { + if (module->wrap(type_uri, ui->type_uri, instance)) { + SUIL_ERRORF("Failed to wrap UI <%s> in type <%s>\n", + ui->uri, type_uri); + suil_instance_free(instance); + return NULL; + } + } else { + instance->host_widget = instance->ui_widget; + } + + return instance; } @@ -151,7 +230,7 @@ SUIL_API LV2UI_Widget suil_instance_get_widget(SuilInstance instance) { - return instance->widget; + return instance->host_widget; } SUIL_API diff --git a/src/qt4_in_gtk2.cpp b/src/qt4_in_gtk2.cpp new file mode 100644 index 0000000..bdacbbd --- /dev/null +++ b/src/qt4_in_gtk2.cpp @@ -0,0 +1,146 @@ +/* Suil, an LV2 plugin UI hosting library. + * Copyright 2011 David Robillard <d@drobilla.net> + * + * Suil 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 3 of the License, or + * (at your option) any later version. + * + * Suil 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 details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gtk/gtk.h> + +#include <QApplication> +#include <QX11EmbedWidget> +#include <QVBoxLayout> + +#include "suil_internal.h" + +extern "C" { + +static int argc = 0; +static QApplication application(argc, NULL, true); + +SUIL_API +int +suil_wrap_init(const char* host_type_uri, + const char* ui_type_uri, + const LV2_Feature* const* features) +{ + return 0; +} + +#define WRAP_TYPE_WIDGET (wrap_widget_get_type()) +#define WRAP_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), WRAP_TYPE_WIDGET, WrapWidget)) +#define WRAP_WIDGET_GET_PRIVATE(obj) \ + G_TYPE_INSTANCE_GET_PRIVATE((obj), \ + WRAP_TYPE_WIDGET, \ + WrapWidgetPrivate) + +typedef struct _WrapWidget WrapWidget; +typedef struct _WrapWidgetClass WrapWidgetClass; +typedef struct _WrapWidgetPrivate WrapWidgetPrivate; + +struct _WrapWidget { + GtkSocket parent_instance; + + WrapWidgetPrivate* priv; +}; + +struct _WrapWidgetClass { + GtkSocketClass parent_class; +}; + +GType wrap_widget_get_type(void); // Accessor for GTK_TYPE_WIDGET + +struct _WrapWidgetPrivate { + QX11EmbedWidget* qembed; + SuilInstance instance; +}; + +G_DEFINE_TYPE(WrapWidget, wrap_widget, GTK_TYPE_SOCKET); + +static void +wrap_widget_dispose(GObject* gobject) +{ + WrapWidget* const self = WRAP_WIDGET(gobject); + WrapWidgetPrivate* const priv = WRAP_WIDGET_GET_PRIVATE(self); + + if (priv->qembed) { + QWidget* const qwidget = (QWidget*)priv->instance->ui_widget; + qwidget->setParent(NULL); + + delete self->priv->qembed; + self->priv->qembed = NULL; + } + + G_OBJECT_CLASS(wrap_widget_parent_class)->dispose(gobject); +} + +static void +wrap_widget_class_init(WrapWidgetClass* klass) +{ + GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->dispose = wrap_widget_dispose; + + g_type_class_add_private(klass, sizeof(WrapWidgetPrivate)); +} + +static void +wrap_widget_init(WrapWidget* self) +{ + WrapWidgetPrivate* const priv = WRAP_WIDGET_GET_PRIVATE(self); + priv->qembed = NULL; + priv->instance = NULL; + self->priv = priv; +} + +static void +wrap_widget_realize(GtkWidget* w, gpointer data) +{ + WrapWidget* const wrap = WRAP_WIDGET(w); + GtkSocket* const s = GTK_SOCKET(w); + WrapWidgetPrivate* const priv = wrap->priv; + + gtk_socket_add_id(s, priv->qembed->winId()); + priv->qembed->show(); +} + +SUIL_API +int +suil_wrap(const char* host_type_uri, + const char* ui_type_uri, + SuilInstance instance) +{ + WrapWidget* const wrap = WRAP_WIDGET(g_object_new(WRAP_TYPE_WIDGET, NULL)); + + WrapWidgetPrivate* const priv = wrap->priv; + priv->qembed = new QX11EmbedWidget(); + priv->instance = instance; + + QWidget* qwidget = (QWidget*)instance->ui_widget; + QVBoxLayout* layout = new QVBoxLayout(priv->qembed); + layout->addWidget(qwidget); + + qwidget->setParent(priv->qembed); + + g_signal_connect_after(G_OBJECT(wrap), + "realize", + G_CALLBACK(wrap_widget_realize), + NULL); + + instance->host_widget = GTK_WIDGET(wrap); + + return 0; +} + +} // extern "C" diff --git a/src/suil_internal.h b/src/suil_internal.h index ed7ebd2..23de999 100644 --- a/src/suil_internal.h +++ b/src/suil_internal.h @@ -47,7 +47,8 @@ struct _SuilInstance { void* lib_handle; const LV2UI_Descriptor* descriptor; LV2UI_Handle handle; - LV2UI_Widget widget; + LV2UI_Widget ui_widget; + LV2UI_Widget host_widget; }; /** Get the UI with the given URI. */ @@ -60,6 +61,20 @@ SuilUI suil_uis_get_best(SuilUIs uis, const char* type_uri); +/** Type of a module's suil_wrap_init function. + * This initialisation function must be called before instantiating any + * UI that will need to be wrapped by this wrapper (e.g. it will perform any + * initialisation required to create a widget for the given toolkit). + */ +typedef int (*SuilWrapInitFunc)(const char* host_type_uri, + const char* ui_type_uri, + const LV2_Feature* const* features); + + +typedef int (*SuilWrapFunc)(const char* host_type_uri, + const char* ui_type_uri, + SuilInstance instance); + typedef void (*SuilVoidFunc)(); /** dlsym wrapper to return a function pointer (without annoying warning) */ @@ -39,9 +39,24 @@ def configure(conf): autowaf.check_header(conf, 'lv2/lv2plug.in/ns/extensions/ui/ui.h') + autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', + atleast_version='2.0.0', mandatory=False) + + autowaf.check_pkg(conf, 'QtGui', uselib_store='QT4', + atleast_version='4.0.0', mandatory=False) + autowaf.define(conf, 'SUIL_VERSION', SUIL_VERSION) + autowaf.define(conf, 'SUIL_MODULE_DIR', conf.env['LIBDIR'] + '/suil') + autowaf.define(conf, 'SUIL_DIR_SEP', '/') + autowaf.define(conf, 'SUIL_MODULE_EXT', '.so') conf.write_config_header('suil-config.h', remove=False) + autowaf.display_msg(conf, "Gtk2 Support", + bool(conf.env['HAVE_GTK2'])) + + autowaf.display_msg(conf, "Qt4 Support", + bool(conf.env['HAVE_QT4'])) + print def build(bld): @@ -62,6 +77,21 @@ def build(bld): obj.install_path = '${LIBDIR}' obj.cflags = [ '-fvisibility=hidden', '-DSUIL_SHARED', '-DSUIL_INTERNAL' ] + if bld.env['HAVE_GTK2'] and bld.env['HAVE_QT4']: + obj = bld(features = 'cxx cxxshlib') + obj.source = 'src/gtk2_in_qt4.cpp' + obj.target = 'suil_gtk2_in_qt4' + obj.install_path = '${LIBDIR}/suil' + obj.cflags = [ '-fvisibility=hidden', '-DSUIL_SHARED', '-DSUIL_INTERNAL' ] + autowaf.use_lib(bld, obj, 'GTK2 QT4') + + obj = bld(features = 'cxx cxxshlib') + obj.source = 'src/qt4_in_gtk2.cpp' + obj.target = 'suil_qt4_in_gtk2' + obj.install_path = '${LIBDIR}/suil' + obj.cflags = [ '-fvisibility=hidden', '-DSUIL_SHARED', '-DSUIL_INTERNAL' ] + autowaf.use_lib(bld, obj, 'GTK2 QT4') + # Documentation autowaf.build_dox(bld, 'SUIL', SUIL_VERSION, top, out) |