// Copyright 2011-2022 David Robillard // Copyright 2018 Rui Nuno Capela // SPDX-License-Identifier: ISC #include "suil_internal.h" #include "warnings.h" #include "lv2/core/lv2.h" #include "lv2/options/options.h" #include "lv2/ui/ui.h" #include "lv2/urid/urid.h" #include "suil/suil.h" SUIL_DISABLE_QT_WARNINGS #include #include #include #include #include SUIL_RESTORE_WARNINGS #undef signals SUIL_DISABLE_GTK_WARNINGS #include #include #include #include #if GTK_MAJOR_VERSION == 3 # include #endif SUIL_RESTORE_WARNINGS #include #include extern "C" { 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 #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)) 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 = nullptr; } if (self->qembed) { self->qembed->deleteLater(); } self->qembed = nullptr; self->idle_iface = nullptr; self->wrapper->impl = nullptr; 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 = nullptr; self->wrapper = nullptr; self->instance = nullptr; self->idle_iface = nullptr; self->idle_id = 0; self->idle_ms = 1000 / 30; // 30 Hz default } static void suil_qt_wrapper_realize(GtkWidget* w, gpointer) { 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(nullptr, Qt::WindowFlags()); wrap->wrapper = wrapper; wrap->instance = instance; auto* qwidget = static_cast(instance->ui_widget); auto* layout = new QVBoxLayout(); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(qwidget, 0, Qt::Alignment()); wrap->qembed->setLayout(layout); g_signal_connect_after( G_OBJECT(wrap), "realize", G_CALLBACK(suil_qt_wrapper_realize), nullptr); instance->host_widget = GTK_WIDGET(wrap); const LV2UI_Idle_Interface* idle_iface = nullptr; if (instance->descriptor->extension_data) { idle_iface = static_cast( 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*, const char*, const char*, LV2_Feature*** features, unsigned n_features) { auto* wrapper = static_cast(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, nullptr)); wrap->wrapper = nullptr; 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, nullptr); // Scan for URID map and options LV2_URID_Map* map = nullptr; LV2_Options_Option* options = nullptr; for (LV2_Feature** f = *features; *f && (!map || !options); ++f) { if (!strcmp((*f)->URI, LV2_OPTIONS__options)) { options = static_cast((*f)->data); } else if (!strcmp((*f)->URI, LV2_URID__map)) { map = static_cast((*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 = static_cast(1000.0f / *static_cast(o->value)); break; } } } return wrapper; } } // extern "C"