lv2 / suil

Library for loading and wrapping LV2 plugin UIs
ISC License
11 stars 5 forks source link

qt5 in gtk3 patch #7

Closed alex-tee closed 5 years ago

alex-tee commented 5 years ago

not comprehensively tested but seems to work fine with *v1 plugins when using the suil code internally (copied inside another project instead of using the shared libraries)

From 8b432505127f4572a4716b09a622f77c06c9dd45 Mon Sep 17 00:00:00 2001
From: Alexandros Theodotou <alex@zrythm.org>
Date: Sun, 17 Feb 2019 15:13:29 +0000
Subject: [PATCH] add qt5_in_gtk3 support

---
 src/instance.c      |   8 ++
 src/qt5_in_gtk3.cpp | 231 ++++++++++++++++++++++++++++++++++++++++++++
 wscript             |  16 +++
 3 files changed, 255 insertions(+)
 create mode 100644 src/qt5_in_gtk3.cpp

diff --git a/src/instance.c b/src/instance.c
index d365ca2..61037fc 100644
--- a/src/instance.c
+++ b/src/instance.c
@@ -54,6 +54,8 @@ suil_ui_supported(const char* host_type_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)
@@ -114,6 +116,12 @@ open_wrapper(SuilHost*      host,
        module_name = "suil_x11_in_gtk3";
    }
 #endif
+#ifdef SUIL_WITH_QT5_IN_GTK3
+   if (!strcmp(container_type_uri, GTK3_UI_URI)
+       && !strcmp(ui_type_uri, QT5_UI_URI)) {
+       module_name = "suil_qt5_in_gtk3";
+   }
+#endif
 #ifdef SUIL_WITH_WIN_IN_GTK2
    if (!strcmp(container_type_uri, GTK2_UI_URI)
        && !strcmp(ui_type_uri, WIN_UI_URI)) {
diff --git a/src/qt5_in_gtk3.cpp b/src/qt5_in_gtk3.cpp
new file mode 100644
index 0000000..ca7677e
--- /dev/null
+++ b/src/qt5_in_gtk3.cpp
@@ -0,0 +1,231 @@
+/*
+  Copyright 2011-2017 David Robillard <http://drobilla.net>
+  Copyright 2018 Rui Nuno Capela <rncbc@rncbc.org>
+
+  Permission to use, copy, modify, and/or distribute this software for any
+  purpose with or without fee is hereby granted, provided that the above
+  copyright notice and this permission notice appear in all copies.
+
+  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include <gtk/gtk.h>
+
+#include <QVBoxLayout>
+#include <QWidget>
+#include <QWindow>
+
+#include <gtk/gtkx.h>
+
+#include "lv2/lv2plug.in/ns/ext/options/options.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+
+#include "./suil_internal.h"
+
+extern "C" {
+
+#define SUIL_TYPE_QT_WRAPPER (suil_qt_wrapper_get_type())
+#define SUIL_QT_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_QT_WRAPPER, SuilQtWrapper))
+
+typedef struct _SuilQtWrapper      SuilQtWrapper;
+typedef struct _SuilQtWrapperClass SuilQtWrapperClass;
+
+struct _SuilQtWrapper {
+   GtkSocket                   socket;
+   QWidget*                    qembed;
+   SuilWrapper*                wrapper;
+   SuilInstance*               instance;
+   const LV2UI_Idle_Interface* idle_iface;
+   guint                       idle_id;
+   guint                       idle_ms;
+};
+
+struct _SuilQtWrapperClass {
+   GtkSocketClass parent_class;
+};
+
+GType suil_qt_wrapper_get_type(void);  // Accessor for SUIL_TYPE_QT_WRAPPER
+
+G_DEFINE_TYPE(SuilQtWrapper, suil_qt_wrapper, GTK_TYPE_SOCKET)
+
+static void
+suil_qt_wrapper_finalize(GObject* gobject)
+{
+   SuilQtWrapper* const self = SUIL_QT_WRAPPER(gobject);
+
+   if (self->idle_id) {
+       g_source_remove(self->idle_id);
+       self->idle_id = 0;
+   }
+
+   if (self->instance->handle) {
+       self->instance->descriptor->cleanup(self->instance->handle);
+       self->instance->handle = NULL;
+   }
+
+   if (self->qembed) {
+       self->qembed->deleteLater();
+   }
+
+   self->qembed        = NULL;
+   self->idle_iface    = NULL;
+   self->wrapper->impl = NULL;
+
+   G_OBJECT_CLASS(suil_qt_wrapper_parent_class)->finalize(gobject);
+}
+
+static void
+suil_qt_wrapper_class_init(SuilQtWrapperClass* klass)
+{
+   GObjectClass* const gobject_class = G_OBJECT_CLASS(klass);
+
+   gobject_class->finalize = suil_qt_wrapper_finalize;
+}
+
+static void
+suil_qt_wrapper_init(SuilQtWrapper* self)
+{
+   self->qembed     = NULL;
+   self->wrapper    = NULL;
+   self->instance   = NULL;
+   self->idle_iface = NULL;
+   self->idle_id    = 0;
+   self->idle_ms    = 1000 / 30;  // 30 Hz default
+}
+
+static void
+suil_qt_wrapper_realize(GtkWidget* w, gpointer data)
+{
+   SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(w);
+   GtkSocket* const     s    = GTK_SOCKET(w);
+   const WId            id   = (WId)gtk_socket_get_id(s);
+
+   wrap->qembed->winId();
+   wrap->qembed->windowHandle()->setParent(QWindow::fromWinId(id));
+   wrap->qembed->show();
+}
+
+static int
+suil_qt_wrapper_resize(LV2UI_Feature_Handle handle, int width, int height)
+{
+   gtk_widget_set_size_request(GTK_WIDGET(handle), width, height);
+
+   return 0;
+}
+
+static gboolean
+suil_qt_wrapper_idle(void* data)
+{
+   SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(data);
+
+   if (wrap->idle_iface) {
+       wrap->idle_iface->idle(wrap->instance->handle);
+       return TRUE;  // Continue calling
+   }
+
+   return FALSE;
+}
+
+static int
+wrapper_wrap(SuilWrapper*  wrapper,
+             SuilInstance* instance)
+{
+   SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(wrapper->impl);
+
+   wrap->qembed   = new QWidget();
+   wrap->wrapper  = wrapper;
+   wrap->instance = instance;
+
+   QWidget*     qwidget = (QWidget*)instance->ui_widget;
+   QVBoxLayout* layout  = new QVBoxLayout();
+   layout->setMargin(0);
+   layout->setSpacing(0);
+   layout->addWidget(qwidget);
+
+   wrap->qembed->setLayout(layout);
+
+   g_signal_connect_after(G_OBJECT(wrap), "realize",
+                          G_CALLBACK(suil_qt_wrapper_realize), NULL);
+
+   instance->host_widget = GTK_WIDGET(wrap);
+
+   const LV2UI_Idle_Interface* idle_iface = NULL;
+   if (instance->descriptor->extension_data) {
+       idle_iface = (const LV2UI_Idle_Interface*)
+           instance->descriptor->extension_data(LV2_UI__idleInterface);
+   }
+
+   if (idle_iface) {
+       wrap->idle_iface = idle_iface;
+       wrap->idle_id    = g_timeout_add(
+           wrap->idle_ms, suil_qt_wrapper_idle, wrap);
+   }
+
+   return 0;
+}
+
+static void
+wrapper_free(SuilWrapper* wrapper)
+{
+   if (wrapper->impl) {
+       SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(wrapper->impl);
+       gtk_widget_destroy(GTK_WIDGET(wrap));
+   }
+}
+
+SUIL_LIB_EXPORT
+SuilWrapper*
+suil_wrapper_new(SuilHost*      host,
+                 const char*    host_type_uri,
+                 const char*    ui_type_uri,
+                 LV2_Feature*** features,
+                 unsigned       n_features)
+{
+   SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper));
+   wrapper->wrap = wrapper_wrap;
+   wrapper->free = wrapper_free;
+
+   SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(
+       g_object_new(SUIL_TYPE_QT_WRAPPER, NULL));
+
+   wrap->wrapper = NULL;
+   wrapper->impl = wrap;
+
+   wrapper->resize.handle    = wrap;
+   wrapper->resize.ui_resize = suil_qt_wrapper_resize;
+
+   suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize);
+   suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL);
+
+   // Scan for URID map and options
+   LV2_URID_Map*       map     = NULL;
+   LV2_Options_Option* options = NULL;
+   for (LV2_Feature** f = *features; *f && (!map || !options); ++f) {
+       if (!strcmp((*f)->URI, LV2_OPTIONS__options)) {
+           options = (LV2_Options_Option*)(*f)->data;
+       } else if (!strcmp((*f)->URI, LV2_URID__map)) {
+           map = (LV2_URID_Map*)(*f)->data;
+       }
+   }
+
+   if (map && options) {
+       // Set UI update rate if given
+       LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate);
+       for (LV2_Options_Option* o = options; o->key; ++o) {
+           if (o->key == ui_updateRate) {
+               wrap->idle_ms = 1000.0f / *(const float*)o->value;
+               break;
+           }
+       }
+   }
+
+   return wrapper;
+}
+
+}  // extern "C"
diff --git a/wscript b/wscript
index 89ab039..8ed5e66 100644
--- a/wscript
+++ b/wscript
@@ -121,6 +121,9 @@ def configure(conf):
     if conf.env.HAVE_GTK3 and conf.env.HAVE_GTK3_X11:
         autowaf.define(conf, 'SUIL_WITH_X11_IN_GTK3', 1)

+    if conf.env.HAVE_GTK3 and conf.env.HAVE_QT5:
+        autowaf.define(conf, 'SUIL_WITH_QT5_IN_GTK3', 1)
+
     if conf.env.HAVE_GTK2 and conf.env.HAVE_GTK2_QUARTZ:
         autowaf.define(conf, 'SUIL_WITH_COCOA_IN_GTK2', 1)

@@ -176,6 +179,7 @@ def configure(conf):
                 ('win', 'gtk2'),
                 ('x11', 'gtk2'),
                 ('x11', 'gtk3'),
+                ('qt5', 'gtk3'),
                 ('x11', 'qt4'),
                 ('x11', 'qt5'),
                 ('cocoa', 'qt5')]
@@ -307,6 +311,18 @@ def build(bld):
                   linkflags    = bld.env.NODELETE_FLAGS)
         autowaf.use_lib(bld, obj, 'GTK3 GTK3_X11 LV2')

+    if bld.env.SUIL_WITH_QT5_IN_GTK3:
+        obj = bld(features     = 'cxx cxxshlib',
+                  source       = 'src/qt5_in_gtk3.cpp',
+                  target       = 'suil_qt5_in_gtk3',
+                  includes     = ['.'],
+                  defines      = ['SUIL_SHARED', 'SUIL_INTERNAL'],
+                  install_path = module_dir,
+                  cflags       = cflags,
+                  lib          = modlib,
+                  linkflags    = bld.env.NODELETE_FLAGS)
+        autowaf.use_lib(bld, obj, 'GTK3 QT5 LV2')
+
     if bld.env.SUIL_WITH_COCOA_IN_GTK2:
         obj = bld(features     = 'cxx cshlib',
                   source       = 'src/cocoa_in_gtk2.mm',
-- 
2.20.1
drobilla commented 5 years ago

Thanks! Though I took a different approach to this since the code is nearly identical: 8fcbdf1