diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cocoa_in_gtk2.mm | 433 | ||||
-rw-r--r-- | src/cocoa_in_qt5.mm | 166 | ||||
-rw-r--r-- | src/gtk2_in_qt4.cpp | 147 | ||||
-rw-r--r-- | src/gtk2_in_qt5.cpp | 157 | ||||
-rw-r--r-- | src/host.c | 90 | ||||
-rw-r--r-- | src/instance.c | 390 | ||||
-rw-r--r-- | src/qt4_in_gtk2.cpp | 154 | ||||
-rw-r--r-- | src/qt5_in_gtk2.cpp | 237 | ||||
-rw-r--r-- | src/suil_internal.h | 185 | ||||
-rw-r--r-- | src/win_in_gtk2.cpp | 258 | ||||
-rw-r--r-- | src/x11.c | 28 | ||||
-rw-r--r-- | src/x11_in_gtk2.c | 398 | ||||
-rw-r--r-- | src/x11_in_gtk3.c | 402 | ||||
-rw-r--r-- | src/x11_in_qt4.cpp | 151 | ||||
-rw-r--r-- | src/x11_in_qt5.cpp | 148 |
15 files changed, 3344 insertions, 0 deletions
diff --git a/src/cocoa_in_gtk2.mm b/src/cocoa_in_gtk2.mm new file mode 100644 index 0000000..3ff87d0 --- /dev/null +++ b/src/cocoa_in_gtk2.mm @@ -0,0 +1,433 @@ +/* + Copyright 2011-2017 David Robillard <http://drobilla.net> + Copyright 2014 Robin Gareus <robin@gareus.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 <string.h> + +#include <gtk/gtk.h> +#include <gdk/gdkquartz.h> + +#include "./suil_internal.h" + +#include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 +#define NSEventTypeFlagsChanged NSFlagsChanged +#define NSEventTypeLeftMouseDown NSLeftMouseDown +#define NSEventTypeLeftMouseDragged NSLeftMouseDragged +#define NSEventTypeLeftMouseUp NSLeftMouseUp +#define NSEventTypeMouseEntered NSMouseEntered +#define NSEventTypeMouseExited NSMouseExited +#define NSEventTypeMouseMoved NSMouseMoved +#define NSEventTypeRightMouseDown NSRightMouseDown +#define NSEventTypeRightMouseUp NSRightMouseUp +#define NSEventTypeScrollWheel NSScrollWheel +#endif + +extern "C" { + +#define SUIL_TYPE_COCOA_WRAPPER (suil_cocoa_wrapper_get_type()) +#define SUIL_COCOA_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_COCOA_WRAPPER, SuilCocoaWrapper)) + +typedef struct _SuilCocoaWrapper SuilCocoaWrapper; +typedef struct _SuilCocoaWrapperClass SuilCocoaWrapperClass; + +struct _SuilCocoaWrapper { + GtkWidget widget; + SuilWrapper* wrapper; + SuilInstance* instance; + + GdkWindow* flt_win; + bool custom_size; + bool mapped; + int req_width; + int req_height; + int alo_width; + int alo_height; + + const LV2UI_Idle_Interface* idle_iface; + guint idle_id; + guint idle_ms; +}; + +struct _SuilCocoaWrapperClass { + GtkWidgetClass parent_class; +}; + +GType suil_cocoa_wrapper_get_type(void); // Accessor for SUIL_TYPE_COCOA_WRAPPER + +G_DEFINE_TYPE(SuilCocoaWrapper, suil_cocoa_wrapper, GTK_TYPE_WIDGET) + +static void +suil_cocoa_wrapper_finalize(GObject* gobject) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(gobject); + + self->wrapper->impl = NULL; + + G_OBJECT_CLASS(suil_cocoa_wrapper_parent_class)->finalize(gobject); +} + +static void +suil_cocoa_realize(GtkWidget* widget) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + g_return_if_fail(self != NULL); + + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + + GdkWindowAttr attrs; + attrs.x = widget->allocation.x; + attrs.y = widget->allocation.y; + attrs.width = widget->allocation.width; + attrs.height = widget->allocation.height; + attrs.wclass = GDK_INPUT_OUTPUT; + attrs.window_type = GDK_WINDOW_CHILD; + attrs.visual = gtk_widget_get_visual(widget); + attrs.colormap = gtk_widget_get_colormap(widget); + attrs.event_mask = gtk_widget_get_events(widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK; + + widget->window = gdk_window_new( + widget->parent->window, + &attrs, + GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP); + + widget->style = gtk_style_attach(widget->style, widget->window); + + gdk_window_set_user_data(widget->window, widget); + gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE); + gtk_widget_queue_resize(widget); +} + +static void +suil_cocoa_size_request(GtkWidget* widget, GtkRequisition* requisition) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + if (self->custom_size) { + requisition->width = self->req_width; + requisition->height = self->req_height; + } else { + NSView* view = (NSView*)self->instance->ui_widget; + NSRect frame = [view frame]; + requisition->width = CGRectGetWidth(NSRectToCGRect(frame)); + requisition->height = CGRectGetHeight(NSRectToCGRect(frame)); + } +} + +static void +suil_cocoa_size_allocate(GtkWidget* widget, GtkAllocation* allocation) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + self->alo_width = allocation->width; + self->alo_height = allocation->height; + + if (!self->mapped) { + return; + } + + gint xx, yy; + gtk_widget_translate_coordinates( + gtk_widget_get_parent(widget), widget, 0, 0, &xx, &yy); + + NSView* view = (NSView*)self->instance->ui_widget; + [view setFrame:NSMakeRect(xx, yy, self->alo_width, self->alo_height)]; +} + +static void +suil_cocoa_map(GtkWidget* widget) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + self->mapped = true; + + if (self->alo_width == 0 || self->alo_height ==0) { + return; + } + + gint xx, yy; + gtk_widget_translate_coordinates( + gtk_widget_get_parent(widget), widget, 0, 0, &xx, &yy); + + NSView* view = (NSView*)self->instance->ui_widget; + [view setHidden:NO]; + [view setFrame:NSMakeRect(xx, yy, self->alo_width, self->alo_height)]; +} + +static void +suil_cocoa_unmap(GtkWidget* widget) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + NSView* view = (NSView*)self->instance->ui_widget; + + self->mapped = false; + [view setHidden:YES]; +} + +static gboolean +suil_cocoa_key_press(GtkWidget* widget, GdkEventKey* event) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + if (!self->instance || !self->wrapper || !self->wrapper->impl) { + return FALSE; + } + NSEvent* nsevent = gdk_quartz_event_get_nsevent((GdkEvent*)event); + NSView* view = (NSView*)self->instance->ui_widget; + [view keyDown:nsevent]; + return TRUE; +} + +static gboolean +suil_cocoa_key_release(GtkWidget* widget, GdkEventKey* event) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + if (!self->instance || !self->wrapper || !self->wrapper->impl) { + return FALSE; + } + NSEvent* nsevent = gdk_quartz_event_get_nsevent((GdkEvent*)event); + NSView* view = (NSView*)self->instance->ui_widget; + [view keyUp:nsevent]; + return TRUE; +} + +static gboolean +suil_cocoa_expose(GtkWidget* widget, GdkEventExpose* event) +{ + SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); + NSView* view = (NSView*)self->instance->ui_widget; + [view drawRect:NSMakeRect(event->area.x, + event->area.y, + event->area.width, + event->area.height)]; + return TRUE; +} + +static void +suil_cocoa_wrapper_class_init(SuilCocoaWrapperClass* klass) +{ + GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* const widget_class = (GtkWidgetClass*)(klass); + + gobject_class->finalize = suil_cocoa_wrapper_finalize; + + widget_class->realize = suil_cocoa_realize; + widget_class->expose_event = suil_cocoa_expose; + widget_class->size_request = suil_cocoa_size_request; + widget_class->size_allocate = suil_cocoa_size_allocate; + widget_class->map = suil_cocoa_map; + widget_class->unmap = suil_cocoa_unmap; + widget_class->key_press_event = suil_cocoa_key_press; + widget_class->key_release_event = suil_cocoa_key_release; +} + +static void +suil_cocoa_wrapper_init(SuilCocoaWrapper* self) +{ + self->wrapper = NULL; + self->instance = NULL; + self->flt_win = NULL; + self->custom_size = false; + self->mapped = false; + self->req_width = self->req_height = 0; + self->alo_width = self->alo_height = 0; + self->idle_iface = NULL; + self->idle_ms = 1000 / 30; // 30 Hz default +} + +static int +wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) +{ + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(handle); + wrap->req_width = width; + wrap->req_height = height; + wrap->custom_size = true; + gtk_widget_queue_resize(GTK_WIDGET(handle)); + return 0; +} + +static gboolean +suil_cocoa_wrapper_idle(void* data) +{ + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(data); + wrap->idle_iface->idle(wrap->instance->handle); + return TRUE; // Continue calling +} + +static GdkFilterReturn +event_filter(GdkXEvent* xevent, GdkEvent* event, gpointer data) +{ + SuilCocoaWrapper* wrap = (SuilCocoaWrapper*)data; + if (!wrap->instance || !wrap->wrapper || !wrap->wrapper->impl) { + return GDK_FILTER_CONTINUE; + } + + NSEvent* nsevent = (NSEvent*)xevent; + NSView* view = (NSView*)wrap->instance->ui_widget; + if (view && nsevent) { + switch([nsevent type]) { + case NSEventTypeFlagsChanged: + [view flagsChanged:nsevent]; + return GDK_FILTER_REMOVE; + case NSEventTypeMouseEntered: + [view mouseEntered:nsevent]; + return GDK_FILTER_REMOVE; + case NSEventTypeMouseExited: + [view mouseExited:nsevent]; + return GDK_FILTER_REMOVE; + + /* Explicitly pass though mouse events. Needed for mouse-drags leaving + the window, and mouse-up after that. */ + case NSEventTypeMouseMoved: + [view mouseMoved:nsevent]; + break; + case NSEventTypeLeftMouseDragged: + [view mouseDragged:nsevent]; + break; + case NSEventTypeLeftMouseDown: + [view mouseDown:nsevent]; + break; + case NSEventTypeLeftMouseUp: + [view mouseUp:nsevent]; + break; + case NSEventTypeRightMouseDown: + [view rightMouseDown:nsevent]; + break; + case NSEventTypeRightMouseUp: + [view rightMouseUp:nsevent]; + break; + case NSEventTypeScrollWheel: + [view scrollWheel:nsevent]; + break; + default: + break; + } + } + return GDK_FILTER_CONTINUE; +} + +static int +wrapper_wrap(SuilWrapper* wrapper, SuilInstance* instance) +{ + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(wrapper->impl); + + instance->host_widget = GTK_WIDGET(wrap); + wrap->wrapper = wrapper; + wrap->instance = instance; + + 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_cocoa_wrapper_idle, wrap); + } + + return 0; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(wrapper->impl); + if (wrap->idle_id) { + g_source_remove(wrap->idle_id); + wrap->idle_id = 0; + } + + gdk_window_remove_filter(wrap->flt_win, event_filter, wrapper->impl); + gtk_object_destroy(GTK_OBJECT(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) +{ + GtkWidget* parent = NULL; + for (unsigned i = 0; i < n_features; ++i) { + if (!strcmp((*features)[i]->URI, LV2_UI__parent)) { + parent = (GtkWidget*)(*features)[i]->data; + } + } + + if (!GTK_CONTAINER(parent)) { + SUIL_ERRORF("No GtkContainer parent given for %s UI\n", + ui_type_uri); + return NULL; + } + + SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER( + g_object_new(SUIL_TYPE_COCOA_WRAPPER, NULL)); + + wrapper->impl = wrap; + wrapper->resize.handle = wrap; + wrapper->resize.ui_resize = wrapper_resize; + + gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(wrap)); + gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE); + gtk_widget_realize(GTK_WIDGET(wrap)); + + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap)); + wrap->flt_win = gtk_widget_get_window(parent); + gdk_window_add_filter(wrap->flt_win, event_filter, wrap); + + NSView* parent_view = gdk_quartz_window_get_nsview(window); + suil_add_feature(features, &n_features, LV2_UI__parent, parent_view); + 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/src/cocoa_in_qt5.mm b/src/cocoa_in_qt5.mm new file mode 100644 index 0000000..4259075 --- /dev/null +++ b/src/cocoa_in_qt5.mm @@ -0,0 +1,166 @@ +/* + Copyright 2017 David Robillard <http://drobilla.net> + + 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. +*/ + +#import <Cocoa/Cocoa.h> + +#include <QCloseEvent> +#include <QMacCocoaViewContainer> +#include <QTimerEvent> +#include <QWidget> + +#undef signals + +#include "./suil_config.h" +#include "./suil_internal.h" + +extern "C" { + +typedef struct { + QWidget* host_widget; + QWidget* parent; +} SuilCocoaInQt5Wrapper; + +class SuilQCocoaWidget : public QMacCocoaViewContainer +{ +public: + SuilQCocoaWidget(NSView* view, QWidget* parent) + : QMacCocoaViewContainer(view, parent) + , _instance(NULL) + , _idle_iface(NULL) + , _ui_timer(0) + { + } + + void start_idle(SuilInstance* instance, + const LV2UI_Idle_Interface* idle_iface) { + _instance = instance; + _idle_iface = idle_iface; + NSView* view = (NSView*)instance->ui_widget; + setCocoaView((NSView*)instance->ui_widget); + setMinimumWidth([view fittingSize].width); + setMinimumHeight([view fittingSize].height); + if (_idle_iface && _ui_timer == 0) { + _ui_timer = this->startTimer(30); + } + } + +protected: + void timerEvent(QTimerEvent* event) { + if (event->timerId() == _ui_timer && _idle_iface) { + _idle_iface->idle(_instance->handle); + } + QWidget::timerEvent(event); + } + + void closeEvent(QCloseEvent* event) { + if (_ui_timer && _idle_iface) { + this->killTimer(_ui_timer); + _ui_timer = 0; + } + QWidget::closeEvent(event); + } + +private: + SuilInstance* _instance; + const LV2UI_Idle_Interface* _idle_iface; + int _ui_timer; +}; + +static void +wrapper_free(SuilWrapper* wrapper) +{ + SuilCocoaInQt5Wrapper* impl = (SuilCocoaInQt5Wrapper*)wrapper->impl; + + if (impl->host_widget) { + delete impl->host_widget; + } + + free(impl); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilCocoaInQt5Wrapper* const impl = (SuilCocoaInQt5Wrapper*)wrapper->impl; + SuilQCocoaWidget* const ew = (SuilQCocoaWidget*)impl->parent; + + if (instance->descriptor->extension_data) { + const LV2UI_Idle_Interface* idle_iface + = (const LV2UI_Idle_Interface*) + instance->descriptor->extension_data(LV2_UI__idleInterface); + ew->start_idle(instance, idle_iface); + } + + impl->host_widget = ew; + + instance->host_widget = impl->host_widget; + + return 0; +} + +static int +wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) +{ + SuilQCocoaWidget* const ew = (SuilQCocoaWidget*)handle; + ew->resize(width, height); + return 0; +} + +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) +{ + QWidget* parent = NULL; + for (unsigned i = 0; i < n_features; ++i) { + if (!strcmp((*features)[i]->URI, LV2_UI__parent)) { + parent = (QWidget*)(*features)[i]->data; + } + } + + if (!parent) { + SUIL_ERRORF("No QWidget parent given for %s UI\n", ui_type_uri); + return NULL; + } + + SuilCocoaInQt5Wrapper* const impl = (SuilCocoaInQt5Wrapper*) + calloc(1, sizeof(SuilCocoaInQt5Wrapper)); + + SuilWrapper* wrapper = (SuilWrapper*)malloc(sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + NSView* view = [NSView new]; + + SuilQCocoaWidget* const ew = new SuilQCocoaWidget(view, parent); + + impl->parent = ew; + + wrapper->impl = impl; + wrapper->resize.handle = ew; + wrapper->resize.ui_resize = wrapper_resize; + + suil_add_feature(features, &n_features, LV2_UI__parent, ew->cocoaView()); + suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize); + suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL); + + return wrapper; +} + +} // extern "C" diff --git a/src/gtk2_in_qt4.cpp b/src/gtk2_in_qt4.cpp new file mode 100644 index 0000000..546f7a9 --- /dev/null +++ b/src/gtk2_in_qt4.cpp @@ -0,0 +1,147 @@ +/* + Copyright 2011-2015 David Robillard <http://drobilla.net> + + 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 <QX11EmbedContainer> +#undef signals + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "./suil_config.h" +#include "./suil_internal.h" + +extern "C" { + +typedef struct _SuilGtk2InQt4Wrapper SuilGtk2InQt4Wrapper; + +struct _SuilGtk2InQt4Wrapper { + QX11EmbedContainer* host_widget; + QWidget* parent; + GtkWidget* plug; +}; + +static void +on_size_request(GtkWidget* widget, + GtkRequisition* requisition, + gpointer user_data) +{ + QX11EmbedContainer* const wrap = (QX11EmbedContainer*)user_data; + wrap->setMinimumSize(requisition->width, requisition->height); +} + +static void +on_size_allocate(GtkWidget* widget, + GdkRectangle* allocation, + gpointer user_data) +{ + QX11EmbedContainer* const wrap = (QX11EmbedContainer*)user_data; + wrap->resize(allocation->width, allocation->height); +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + SuilGtk2InQt4Wrapper* impl = (SuilGtk2InQt4Wrapper*)wrapper->impl; + + if (impl->plug) { + gtk_widget_destroy(impl->plug); + } + + if (impl->host_widget) { + delete impl->host_widget; + } + + free(impl); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilGtk2InQt4Wrapper* const impl = (SuilGtk2InQt4Wrapper*)wrapper->impl; + QWidget* root = static_cast<QWidget*>(impl->parent); + QX11EmbedContainer* const wrap = new QX11EmbedContainer(root); + GtkWidget* const plug = gtk_plug_new(wrap->winId()); + GtkWidget* const widget = (GtkWidget*)instance->ui_widget; + + gtk_container_add(GTK_CONTAINER(plug), widget); + gtk_widget_show_all(plug); + +#ifdef SUIL_OLD_GTK + wrap->resize(widget->allocation.width, widget->allocation.height); +#else + GtkAllocation alloc; + gtk_widget_get_allocation(widget, &alloc); + wrap->resize(alloc.width, alloc.height); +#endif + + g_signal_connect( + G_OBJECT(plug), "size-request", G_CALLBACK(on_size_request), wrap); + + g_signal_connect( + G_OBJECT(plug), "size-allocate", G_CALLBACK(on_size_allocate), wrap); + + impl->host_widget = wrap; + impl->plug = plug; + instance->host_widget = wrap; + + return 0; +} + +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) +{ + /* We have to open libgtk here, so Gtk type symbols are present and will be + found by the introspection stuff. This is required at least to make + GtkBuilder use in UIs work, otherwise they will cause "Invalid object + type" errors. + */ + if (!host->gtk_lib) { + dlerror(); + host->gtk_lib = dlopen(SUIL_GTK2_LIB_NAME, RTLD_LAZY|RTLD_GLOBAL); + if (!host->gtk_lib) { + SUIL_ERRORF("Failed to open %s (%s)\n", + SUIL_GTK2_LIB_NAME, dlerror()); + return NULL; + } + gtk_init(NULL, NULL); + } + + /* Create wrapper implementation. */ + SuilGtk2InQt4Wrapper* const impl = (SuilGtk2InQt4Wrapper*) + calloc(1, sizeof(SuilGtk2InQt4Wrapper)); + + /* Set parent widget if given. */ + for (unsigned i = 0; i < n_features; ++i) { + if (!strcmp((*features)[i]->URI, LV2_UI__parent)) { + impl->parent = static_cast<QWidget*>((*features)[i]->data); + } + } + + SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + wrapper->impl = impl; + + return wrapper; +} + +} // extern "C" diff --git a/src/gtk2_in_qt5.cpp b/src/gtk2_in_qt5.cpp new file mode 100644 index 0000000..77af460 --- /dev/null +++ b/src/gtk2_in_qt5.cpp @@ -0,0 +1,157 @@ +/* + Copyright 2011-2015 David Robillard <http://drobilla.net> + Copyright 2015 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 <QVBoxLayout> +#include <QWidget> +#include <QWindow> + +#undef signals + +#include <gtk/gtk.h> + +#include "./suil_config.h" +#include "./suil_internal.h" + +extern "C" { + +typedef struct _SuilGtk2InQt5Wrapper SuilGtk2InQt5Wrapper; + +struct _SuilGtk2InQt5Wrapper { + QWidget* host_widget; + QWindow* window; + GtkWidget* plug; +}; + +static void +on_size_request(GtkWidget* widget, + GtkRequisition* requisition, + gpointer user_data) +{ + QWidget* const wrap = (QWidget*)user_data; + wrap->setMinimumSize(requisition->width, requisition->height); +} + +static void +on_size_allocate(GtkWidget* widget, + GdkRectangle* allocation, + gpointer user_data) +{ + QWidget* const wrap = (QWidget*)user_data; + wrap->resize(allocation->width, allocation->height); +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + SuilGtk2InQt5Wrapper* impl = (SuilGtk2InQt5Wrapper*)wrapper->impl; + + if (impl->window) { + impl->window->setParent(NULL); + delete impl->window; + } + + if (impl->plug) { + gtk_widget_destroy(impl->plug); + } + + if (impl->host_widget) { + delete impl->host_widget; + } + + free(impl); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilGtk2InQt5Wrapper* const impl = (SuilGtk2InQt5Wrapper*)wrapper->impl; + QWidget* const wrap = new QWidget(NULL, Qt::Window); + GtkWidget* const plug = gtk_plug_new(0); + GtkWidget* const widget = (GtkWidget*)instance->ui_widget; + + gtk_container_add(GTK_CONTAINER(plug), widget); + gtk_widget_show_all(plug); + + const WId wid = (const WId)gtk_plug_get_id((GtkPlug*)plug); + QWindow* window = QWindow::fromWinId(wid); + QWidget* container = QWidget::createWindowContainer(window, wrap); + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(container); + wrap->setLayout(layout); + +#ifdef SUIL_OLD_GTK + wrap->resize(widget->allocation.width, widget->allocation.height); +#else + GtkAllocation alloc; + gtk_widget_get_allocation(widget, &alloc); + wrap->resize(alloc.width, alloc.height); +#endif + + g_signal_connect( + G_OBJECT(plug), "size-request", G_CALLBACK(on_size_request), wrap); + + g_signal_connect( + G_OBJECT(plug), "size-allocate", G_CALLBACK(on_size_allocate), wrap); + + impl->host_widget = wrap; + impl->window = window; + impl->plug = plug; + instance->host_widget = wrap; + + return 0; +} + +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) +{ + /* We have to open libgtk here, so Gtk type symbols are present and will be + found by the introspection stuff. This is required at least to make + GtkBuilder use in UIs work, otherwise they will cause "Invalid object + type" errors. + */ + if (!host->gtk_lib) { + dlerror(); + host->gtk_lib = dlopen(SUIL_GTK2_LIB_NAME, RTLD_LAZY|RTLD_GLOBAL); + if (!host->gtk_lib) { + SUIL_ERRORF("Failed to open %s (%s)\n", + SUIL_GTK2_LIB_NAME, dlerror()); + return NULL; + } + gtk_init(NULL, NULL); + } + + /* Create wrapper implementation. */ + SuilGtk2InQt5Wrapper* const impl = (SuilGtk2InQt5Wrapper*) + calloc(1, sizeof(SuilGtk2InQt5Wrapper)); + + SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + wrapper->impl = impl; + + return wrapper; +} + +} // extern "C" diff --git a/src/host.c b/src/host.c new file mode 100644 index 0000000..f64f9c5 --- /dev/null +++ b/src/host.c @@ -0,0 +1,90 @@ +/* + Copyright 2011-2017 David Robillard <http://drobilla.net> + Copyright 2017 Stefan Westerfeld <stefan@space.twc.de> + + 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 "./suil_internal.h" + +int suil_argc = 0; +char** suil_argv = NULL; + +SUIL_API +SuilHost* +suil_host_new(SuilPortWriteFunc write_func, + SuilPortIndexFunc index_func, + SuilPortSubscribeFunc subscribe_func, + SuilPortUnsubscribeFunc unsubscribe_func) +{ + SuilHost* host = (SuilHost*)calloc(1, sizeof(struct SuilHostImpl)); + host->write_func = write_func; + host->index_func = index_func; + host->subscribe_func = subscribe_func; + host->unsubscribe_func = unsubscribe_func; + host->argc = suil_argc; + host->argv = suil_argv; + return host; +} + +SUIL_API +void +suil_host_set_touch_func(SuilHost* host, + SuilTouchFunc touch_func) +{ + host->touch_func = touch_func; +} + +SUIL_API +void +suil_host_free(SuilHost* host) +{ + if (host) { + if (host->gtk_lib) { + dlclose(host->gtk_lib); + } + free(host); + } +} + +#ifdef SUIL_WITH_X11 +static void +suil_load_init_module(const char* module_name) +{ + void* const lib = suil_open_module(module_name); + if (!lib) { + return; + } + + SuilVoidFunc init_func = suil_dlfunc(lib, "suil_host_init"); + if (init_func) { + (*init_func)(); + } else { + SUIL_ERRORF("Corrupt init module %s\n", module_name); + } + + dlclose(lib); +} +#endif + +SUIL_API +void +suil_init(int* argc, char*** argv, SuilArg key, ...) +{ + suil_argc = argc ? *argc : 0; + suil_argv = argv ? *argv : NULL; + +#ifdef SUIL_WITH_X11 + suil_load_init_module("suil_x11"); +#endif +} diff --git a/src/instance.c b/src/instance.c new file mode 100644 index 0000000..d365ca2 --- /dev/null +++ b/src/instance.c @@ -0,0 +1,390 @@ +/* + Copyright 2007-2017 David Robillard <http://drobilla.net> + + 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "./suil_config.h" +#include "./suil_internal.h" + +#define GTK2_UI_URI LV2_UI__GtkUI +#define GTK3_UI_URI LV2_UI__Gtk3UI +#define QT4_UI_URI LV2_UI__Qt4UI +#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; + } else if ((!strcmp(host_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, QT4_UI_URI)) + || (!strcmp(host_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, QT5_UI_URI)) + || (!strcmp(host_type_uri, QT4_UI_URI) + && !strcmp(ui_type_uri, GTK2_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, 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, QT4_UI_URI) + && !strcmp(ui_type_uri, X11_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; + } else { + 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; +#ifdef SUIL_WITH_GTK2_IN_QT4 + if (!strcmp(container_type_uri, QT4_UI_URI) + && !strcmp(ui_type_uri, GTK2_UI_URI)) { + module_name = "suil_gtk2_in_qt4"; + } +#endif +#ifdef SUIL_WITH_GTK2_IN_QT5 + if (!strcmp(container_type_uri, QT5_UI_URI) + && !strcmp(ui_type_uri, GTK2_UI_URI)) { + module_name = "suil_gtk2_in_qt5"; + } +#endif +#ifdef SUIL_WITH_QT4_IN_GTK2 + if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, QT4_UI_URI)) { + module_name = "suil_qt4_in_gtk2"; + } +#endif +#ifdef SUIL_WITH_QT5_IN_GTK2 + if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, QT5_UI_URI)) { + module_name = "suil_qt5_in_gtk2"; + } +#endif +#ifdef SUIL_WITH_X11_IN_GTK2 + if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, X11_UI_URI)) { + module_name = "suil_x11_in_gtk2"; + } +#endif +#ifdef SUIL_WITH_X11_IN_GTK3 + if (!strcmp(container_type_uri, GTK3_UI_URI) + && !strcmp(ui_type_uri, X11_UI_URI)) { + module_name = "suil_x11_in_gtk3"; + } +#endif +#ifdef SUIL_WITH_WIN_IN_GTK2 + if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, WIN_UI_URI)) { + module_name = "suil_win_in_gtk2"; + } +#endif +#ifdef SUIL_WITH_COCOA_IN_GTK2 + if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, COCOA_UI_URI)) { + module_name = "suil_cocoa_in_gtk2"; + } +#endif +#ifdef SUIL_WITH_X11_IN_QT4 + if (!strcmp(container_type_uri, QT4_UI_URI) + && !strcmp(ui_type_uri, X11_UI_URI)) { + module_name = "suil_x11_in_qt4"; + } +#endif +#ifdef SUIL_WITH_X11_IN_QT5 + if (!strcmp(container_type_uri, QT5_UI_URI) + && !strcmp(ui_type_uri, X11_UI_URI)) { + module_name = "suil_x11_in_qt5"; + } +#endif +#ifdef SUIL_WITH_COCOA_IN_QT5 + if (!strcmp(container_type_uri, QT5_UI_URI) + && !strcmp(ui_type_uri, COCOA_UI_URI)) { + module_name = "suil_cocoa_in_qt5"; + } +#endif + + 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); + dlclose(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 + dlerror(); + void* lib = dlopen(ui_binary_path, RTLD_NOW); + if (!lib) { + SUIL_ERRORF("Unable to open UI library %s (%s)\n", + ui_binary_path, dlerror()); + 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); + dlclose(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; + } else 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); + dlclose(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); + dlclose(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); + } + + dlclose(instance->lib_handle); + + // Close libraries and free everything + if (instance->wrapper) { +#ifndef _WIN32 + // Never unload modules on windows, causes mysterious segfaults + dlclose(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; +} diff --git a/src/qt4_in_gtk2.cpp b/src/qt4_in_gtk2.cpp new file mode 100644 index 0000000..fbec381 --- /dev/null +++ b/src/qt4_in_gtk2.cpp @@ -0,0 +1,154 @@ +/* + Copyright 2011-2017 David Robillard <http://drobilla.net> + + 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 <QApplication> +#include <QVBoxLayout> +#include <QX11EmbedWidget> + +#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; + QApplication* app; + QX11EmbedWidget* qembed; + SuilWrapper* wrapper; + SuilInstance* instance; +}; + +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->instance->handle) { + self->instance->descriptor->cleanup(self->instance->handle); + self->instance->handle = NULL; + } + + delete self->qembed; + + self->qembed = NULL; + self->app = 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->app = NULL; + self->qembed = NULL; + self->instance = NULL; +} + +static void +suil_qt_wrapper_realize(GtkWidget* w, gpointer data) +{ + SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(w); + GtkSocket* const s = GTK_SOCKET(w); + + gtk_socket_add_id(s, wrap->qembed->winId()); + wrap->qembed->show(); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(wrapper->impl); + + wrap->qembed = new QX11EmbedWidget(); + wrap->wrapper = wrapper; + wrap->instance = instance; + + QWidget* qwidget = (QWidget*)instance->ui_widget; + QVBoxLayout* layout = new QVBoxLayout(wrap->qembed); + layout->addWidget(qwidget); + + qwidget->setParent(wrap->qembed); + + g_signal_connect_after(G_OBJECT(wrap), "realize", + G_CALLBACK(suil_qt_wrapper_realize), NULL); + + instance->host_widget = GTK_WIDGET(wrap); + + return 0; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilQtWrapper* const wrap = SUIL_QT_WRAPPER(wrapper->impl); + gtk_object_destroy(GTK_OBJECT(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)); + + if (qApp) { + wrap->app = qApp; + } else { + wrap->app = new QApplication(host->argc, host->argv, true); + } + + wrap->wrapper = NULL; + wrapper->impl = wrap; + + return wrapper; +} + +} // extern "C" diff --git a/src/qt5_in_gtk2.cpp b/src/qt5_in_gtk2.cpp new file mode 100644 index 0000000..a03e8a7 --- /dev/null +++ b/src/qt5_in_gtk2.cpp @@ -0,0 +1,237 @@ +/* + 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 <QApplication> +#include <QVBoxLayout> +#include <QWidget> +#include <QWindow> + +#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; + QApplication* app; + 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; + } + + delete self->qembed; + + self->qembed = NULL; + self->app = 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->app = NULL; + 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 = (const 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_object_destroy(GTK_OBJECT(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)); + + if (qApp) { + wrap->app = qApp; + } else { + wrap->app = new QApplication(host->argc, host->argv, true); + } + + 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/src/suil_internal.h b/src/suil_internal.h new file mode 100644 index 0000000..ecafbcf --- /dev/null +++ b/src/suil_internal.h @@ -0,0 +1,185 @@ +/* + Copyright 2007-2017 David Robillard <http://drobilla.net> + + 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. +*/ + +#ifndef SUIL_INTERNAL_H +#define SUIL_INTERNAL_H + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#include <windows.h> +#define dlopen(path, flags) LoadLibrary(path) +#define dlclose(lib) FreeLibrary((HMODULE)lib) +#define inline __inline +#define snprintf _snprintf +static inline char* dlerror(void) { return "Unknown error"; } +#else +#include <dlfcn.h> +#endif + +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#include "suil/suil.h" +#include "./suil_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SUIL_ERRORF(fmt, ...) fprintf(stderr, "suil error: " fmt, __VA_ARGS__) + +struct SuilHostImpl { + SuilPortWriteFunc write_func; + SuilPortIndexFunc index_func; + SuilPortSubscribeFunc subscribe_func; + SuilPortUnsubscribeFunc unsubscribe_func; + SuilTouchFunc touch_func; + void* gtk_lib; + int argc; + char** argv; +}; + +struct _SuilWrapper; + +typedef void (*SuilWrapperFreeFunc)(struct _SuilWrapper*); + +typedef int (*SuilWrapperWrapFunc)(struct _SuilWrapper* wrapper, + SuilInstance* instance); + +typedef struct _SuilWrapper { + SuilWrapperWrapFunc wrap; + SuilWrapperFreeFunc free; + void* lib; + void* impl; + LV2UI_Resize resize; +} SuilWrapper; + +struct SuilInstanceImpl { + void* lib_handle; + const LV2UI_Descriptor* descriptor; + LV2UI_Handle handle; + SuilWrapper* wrapper; + LV2_Feature** features; + LV2UI_Port_Map port_map; + LV2UI_Port_Subscribe port_subscribe; + LV2UI_Touch touch; + SuilWidget ui_widget; + SuilWidget host_widget; +}; + +/** + The type of the suil_wrapper_new entry point in a wrapper module. + + This constructs a SuilWrapper which contains everything necessary + to wrap a widget, including a possibly extended features array to + be used for instantiating the UI. +*/ +typedef SuilWrapper* (*SuilWrapperNewFunc)(SuilHost* host, + const char* host_type_uri, + const char* ui_type_uri, + LV2_Feature*** features, + unsigned n_features); + +/** Prototype for suil_wrapper_new in each wrapper module. */ +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); + +/** Prototype for suil_host_init in each init module. */ +SUIL_LIB_EXPORT +void +suil_host_init(void); + +/** Dynamically load the suil module with the given name. */ +static inline void* +suil_open_module(const char* module_name) +{ + const char* const env_dir = getenv("SUIL_MODULE_DIR"); + const char* const mod_dir = env_dir ? env_dir : SUIL_MODULE_DIR; + const size_t path_len = strlen(mod_dir) + + strlen(SUIL_DIR_SEP SUIL_MODULE_PREFIX SUIL_MODULE_EXT) + + strlen(module_name) + + 2; + + char* const path = (char*)calloc(path_len, 1); + snprintf(path, path_len, "%s%s%s%s%s", + mod_dir, SUIL_DIR_SEP, + SUIL_MODULE_PREFIX, module_name, SUIL_MODULE_EXT); + + dlerror(); + void* lib = dlopen(path, RTLD_NOW); + if (!lib) { + SUIL_ERRORF("Failed to open module %s (%s)\n", path, dlerror()); + } + + free(path); + return lib; +} + +typedef void (*SuilVoidFunc)(void); + +/** dlsym wrapper to return a function pointer (without annoying warning) */ +static inline SuilVoidFunc +suil_dlfunc(void* handle, const char* symbol) +{ +#ifdef _WIN32 + return (SuilVoidFunc)GetProcAddress((HMODULE)handle, symbol); +#else + typedef SuilVoidFunc (*VoidFuncGetter)(void*, const char*); + VoidFuncGetter dlfunc = (VoidFuncGetter)dlsym; + return dlfunc(handle, symbol); +#endif +} + +/** Add a feature to a (mutable) LV2 feature array. */ +static inline void +suil_add_feature(LV2_Feature*** features, + unsigned* n, + const char* uri, + void* data) +{ + for (unsigned i = 0; i < *n && (*features)[i]; ++i) { + if (!strcmp((*features)[i]->URI, uri)) { + (*features)[i]->data = data; + return; + } + } + + *features = (LV2_Feature**)realloc(*features, + sizeof(LV2_Feature*) * (*n + 2)); + + (*features)[*n] = (LV2_Feature*)malloc(sizeof(LV2_Feature)); + (*features)[*n]->URI = uri; + (*features)[*n]->data = data; + (*features)[*n + 1] = NULL; + *n += 1; +} + +extern int suil_argc; +extern char** suil_argv; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // SUIL_INTERNAL_H diff --git a/src/win_in_gtk2.cpp b/src/win_in_gtk2.cpp new file mode 100644 index 0000000..70c7c1a --- /dev/null +++ b/src/win_in_gtk2.cpp @@ -0,0 +1,258 @@ +/* + Copyright 2011-2015 David Robillard <http://drobilla.net> + + 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 <string.h> + +#include <gtk/gtk.h> +#include <gdk/gdkwin32.h> + +#ifndef WM_MOUSEWHEEL +# define WM_MOUSEWHEEL 0x020A +#endif +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL 0x020E +#endif + +#include "./suil_internal.h" + +#include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +extern "C" { + +#define SUIL_TYPE_WIN_WRAPPER (suil_win_wrapper_get_type()) +#define SUIL_WIN_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_WIN_WRAPPER, SuilWinWrapper)) + +typedef struct _SuilWinWrapper SuilWinWrapper; +typedef struct _SuilWinWrapperClass SuilWinWrapperClass; + +struct _SuilWinWrapper { + GtkDrawingArea area; + SuilWrapper* wrapper; + SuilInstance* instance; + GdkWindow* flt_win; + const LV2UI_Idle_Interface* idle_iface; + guint idle_id; + guint idle_ms; +}; + +struct _SuilWinWrapperClass { + GtkDrawingAreaClass parent_class; +}; + +GType suil_win_wrapper_get_type(void); // Accessor for SUIL_TYPE_WIN_WRAPPER + +G_DEFINE_TYPE(SuilWinWrapper, suil_win_wrapper, GTK_TYPE_DRAWING_AREA) + +static void +suil_win_wrapper_finalize(GObject* gobject) +{ + SuilWinWrapper* const self = SUIL_WIN_WRAPPER(gobject); + + self->wrapper->impl = NULL; + self->instance = NULL; + + G_OBJECT_CLASS(suil_win_wrapper_parent_class)->finalize(gobject); +} + +static void +suil_win_size_allocate(GtkWidget* widget, GtkAllocation* allocation) +{ + SuilWinWrapper* const self = SUIL_WIN_WRAPPER(widget); + g_return_if_fail(self != NULL); + + widget->allocation = *allocation; + if (gtk_widget_get_realized(widget)) { + gdk_window_move_resize(widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + RECT wr = { 0, 0, (long)allocation->width, (long)allocation->height }; + AdjustWindowRectEx(&wr, WS_CHILD, FALSE, WS_EX_TOPMOST); + + SetWindowPos((HWND)self->instance->ui_widget, HWND_NOTOPMOST, + 0, 0, wr.right - wr.left, wr.bottom - wr.top, + SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); + UpdateWindow((HWND)self->instance->ui_widget); + PostMessage((HWND)self->instance->ui_widget, WM_PAINT, 0, 0); + } +} + +static void +suil_win_wrapper_class_init(SuilWinWrapperClass* klass) +{ + GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* const widget_class = (GtkWidgetClass*)(klass); + + widget_class->size_allocate = suil_win_size_allocate; + gobject_class->finalize = suil_win_wrapper_finalize; +} + +static void +suil_win_wrapper_init(SuilWinWrapper* self) +{ + self->instance = NULL; + self->flt_win = NULL; + self->idle_iface = NULL; + self->idle_ms = 1000 / 30; // 30 Hz default +} + +static gboolean +suil_win_wrapper_idle(void* data) +{ + SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(data); + wrap->idle_iface->idle(wrap->instance->handle); + return TRUE; // Continue calling +} + +static int +wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) +{ + gtk_drawing_area_size(GTK_DRAWING_AREA(handle), width, height); + return 0; +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(wrapper->impl); + + instance->host_widget = GTK_WIDGET(wrap); + wrap->wrapper = wrapper; + wrap->instance = instance; + + 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_win_wrapper_idle, wrap); + } + + return 0; +} + +static GdkFilterReturn +event_filter(GdkXEvent* xevent, GdkEvent* event, gpointer data) +{ + SuilWinWrapper* wrap = (SuilWinWrapper*)data; + MSG* msg = (MSG*)xevent; + if (msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) { + // Forward keyboard events to UI window + PostMessage((HWND)wrap->instance->ui_widget, + msg->message, msg->wParam, msg->lParam); + return GDK_FILTER_REMOVE; + } else if (msg->message == WM_MOUSEWHEEL || msg->message == WM_MOUSEHWHEEL) { + PostMessage((HWND)wrap->instance->ui_widget, + msg->message, msg->wParam, msg->lParam); + return GDK_FILTER_REMOVE; + } + return GDK_FILTER_CONTINUE; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(wrapper->impl); + if (wrap->idle_id) { + g_source_remove(wrap->idle_id); + wrap->idle_id = 0; + } + + gdk_window_remove_filter(wrap->flt_win, event_filter, wrapper->impl); + gtk_object_destroy(GTK_OBJECT(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) +{ + GtkWidget* parent = NULL; + for (unsigned i = 0; i < n_features; ++i) { + if (!strcmp((*features)[i]->URI, LV2_UI__parent)) { + parent = (GtkWidget*)(*features)[i]->data; + } + } + + if (!GTK_CONTAINER(parent)) { + SUIL_ERRORF("No GtkContainer parent given for %s UI\n", + ui_type_uri); + return NULL; + } + + SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + + SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER( + g_object_new(SUIL_TYPE_WIN_WRAPPER, NULL)); + + wrap->wrapper = NULL; + + wrapper->impl = wrap; + wrapper->resize.handle = wrap; + wrapper->resize.ui_resize = wrapper_resize; + + gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(wrap)); + gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE); + gtk_widget_realize(GTK_WIDGET(wrap)); + + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap)); + + wrap->flt_win = gtk_widget_get_window(parent); + gdk_window_add_filter(wrap->flt_win, event_filter, wrap); + + HWND parent_window = (HWND)GDK_WINDOW_HWND(window); + suil_add_feature(features, &n_features, LV2_UI__parent, parent_window); + 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/src/x11.c b/src/x11.c new file mode 100644 index 0000000..4c8a15a --- /dev/null +++ b/src/x11.c @@ -0,0 +1,28 @@ +/* + Copyright 2017 David Robillard <http://drobilla.net> + Copyright 2017 Stefan Westerfeld <stefan@space.twc.de> + + 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 <X11/Xlib.h> + +#include "./suil_internal.h" + +SUIL_LIB_EXPORT +void +suil_host_init(void) +{ + // This must be called first for Qt5 in Gtk2 to function correctly + XInitThreads(); +} diff --git a/src/x11_in_gtk2.c b/src/x11_in_gtk2.c new file mode 100644 index 0000000..7ec9592 --- /dev/null +++ b/src/x11_in_gtk2.c @@ -0,0 +1,398 @@ +/* + Copyright 2011-2016 David Robillard <http://drobilla.net> + + 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 <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#include "./suil_internal.h" + +#define SUIL_TYPE_X11_WRAPPER (suil_x11_wrapper_get_type()) +#define SUIL_X11_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_X11_WRAPPER, SuilX11Wrapper)) + +typedef struct _SuilX11Wrapper SuilX11Wrapper; +typedef struct _SuilX11WrapperClass SuilX11WrapperClass; + +struct _SuilX11Wrapper { + GtkSocket socket; + GtkPlug* plug; + SuilWrapper* wrapper; + SuilInstance* instance; + const LV2UI_Idle_Interface* idle_iface; + guint idle_id; + guint idle_ms; +}; + +struct _SuilX11WrapperClass { + GtkSocketClass parent_class; +}; + +GType suil_x11_wrapper_get_type(void); // Accessor for SUIL_TYPE_X11_WRAPPER + +G_DEFINE_TYPE(SuilX11Wrapper, suil_x11_wrapper, GTK_TYPE_SOCKET) + +/** + Check if 'swallowed' subwindow is known to the X server. + + Gdk/GTK can mark the window as realized, mapped and visible even though + there is no window-ID on the X server for it yet. Then, + suil_x11_on_size_allocate() will cause a "BadWinow" X error. +*/ +static bool +x_window_is_valid(SuilX11Wrapper* socket) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + Window root = 0; + Window parent = 0; + Window* children = NULL; + unsigned childcount = 0; + + XQueryTree(GDK_WINDOW_XDISPLAY(window), + GDK_WINDOW_XID(window), + &root, &parent, &children, &childcount); + for (unsigned i = 0; i < childcount; ++i) { + if (children[i] == (Window)socket->instance->ui_widget) { + XFree(children); + return true; + } + } + XFree(children); + return false; +} + +static gboolean +on_plug_removed(GtkSocket* sock, gpointer data) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(sock); + + 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; + } + + self->plug = NULL; + return TRUE; +} + +static void +suil_x11_wrapper_finalize(GObject* gobject) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(gobject); + + self->wrapper->impl = NULL; + + G_OBJECT_CLASS(suil_x11_wrapper_parent_class)->finalize(gobject); +} + +static void +suil_x11_wrapper_realize(GtkWidget* w) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w); + GtkSocket* const socket = GTK_SOCKET(w); + + if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize) { + GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize(w); + } + + gtk_socket_add_id(socket, gtk_plug_get_id(wrap->plug)); + + gtk_widget_set_sensitive(GTK_WIDGET(wrap->plug), TRUE); + gtk_widget_set_can_focus(GTK_WIDGET(wrap->plug), TRUE); + gtk_widget_grab_focus(GTK_WIDGET(wrap->plug)); +} + +static void +suil_x11_wrapper_show(GtkWidget* w) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w); + + if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show) { + GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show(w); + } + + gtk_widget_show(GTK_WIDGET(wrap->plug)); +} + +static gboolean +forward_key_event(SuilX11Wrapper* socket, + GdkEvent* gdk_event) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + GdkScreen* screen = gdk_visual_get_screen(gdk_window_get_visual(window)); + + Window target_window; + if (gdk_event->any.window == window) { + // Event sent up to the plug window, forward it up to the parent + GtkWidget* widget = GTK_WIDGET(socket->instance->host_widget); + GdkWindow* parent = gtk_widget_get_parent_window(widget); + if (parent) { + target_window = GDK_WINDOW_XID(parent); + } else { + return FALSE; // Wrapper is a top-level window, do nothing + } + } else { + // Event sent anywhere else, send to the plugin + target_window = (Window)socket->instance->ui_widget; + } + + XKeyEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease; + xev.root = GDK_WINDOW_XID(gdk_screen_get_root_window(screen)); + xev.window = target_window; + xev.subwindow = None; + xev.time = gdk_event->key.time; + xev.state = gdk_event->key.state; + xev.keycode = gdk_event->key.hardware_keycode; + + XSendEvent(GDK_WINDOW_XDISPLAY(window), + target_window, + False, + NoEventMask, + (XEvent*)&xev); + + return (gdk_event->any.window != window); +} + +static gboolean +idle_size_request(gpointer user_data) +{ + GtkWidget* w = GTK_WIDGET(user_data); + gtk_widget_queue_resize(w); + return FALSE; +} + +static void +forward_size_request(SuilX11Wrapper* socket, + GtkAllocation* allocation) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + if (x_window_is_valid(socket)) { + // Calculate allocation size constrained to X11 limits for widget + int width = allocation->width; + int height = allocation->height; + XSizeHints hints; + memset(&hints, 0, sizeof(hints)); + XGetNormalHints(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + &hints); + if (hints.flags & PMaxSize) { + width = MIN(width, hints.max_width); + height = MIN(height, hints.max_height); + } + if (hints.flags & PMinSize) { + width = MAX(width, hints.min_width); + height = MAX(height, hints.min_height); + } + + // Resize widget window + XResizeWindow(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + width, height); + + // Get actual widget geometry + Window root; + int wx, wy; + unsigned int ww, wh; + unsigned int ignored; + XGetGeometry(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + &root, + &wx, &wy, &ww, &wh, + &ignored, &ignored); + + // Center widget in allocation + wx = (allocation->width - ww) / 2; + wy = (allocation->height - wh) / 2; + XMoveWindow(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + wx, wy); + } else { + /* Child has not been realized, so unable to resize now. + Queue an idle resize. */ + g_idle_add(idle_size_request, socket->plug); + } +} + +static gboolean +suil_x11_wrapper_key_event(GtkWidget* widget, + GdkEventKey* event) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget); + + if (self->plug) { + return forward_key_event(self, (GdkEvent*)event); + } + + return FALSE; +} + +static void +suil_x11_on_size_allocate(GtkWidget* widget, + GtkAllocation* a) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget); + + if (self->plug + && GTK_WIDGET_REALIZED(widget) + && GTK_WIDGET_MAPPED(widget) + && GTK_WIDGET_VISIBLE(widget)) { + forward_size_request(self, a); + } +} + +static void +suil_x11_wrapper_class_init(SuilX11WrapperClass* klass) +{ + GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* const widget_class = GTK_WIDGET_CLASS(klass); + + gobject_class->finalize = suil_x11_wrapper_finalize; + widget_class->realize = suil_x11_wrapper_realize; + widget_class->show = suil_x11_wrapper_show; + widget_class->key_press_event = suil_x11_wrapper_key_event; + widget_class->key_release_event = suil_x11_wrapper_key_event; +} + +static void +suil_x11_wrapper_init(SuilX11Wrapper* self) +{ + self->plug = GTK_PLUG(gtk_plug_new(0)); + self->wrapper = NULL; + self->instance = NULL; + self->idle_iface = NULL; + self->idle_ms = 1000 / 30; // 30 Hz default +} + +static int +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_x11_wrapper_idle(void* data) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(data); + + wrap->idle_iface->idle(wrap->instance->handle); + + return TRUE; // Continue calling +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl); + + instance->host_widget = GTK_WIDGET(wrap); + wrap->wrapper = wrapper; + wrap->instance = instance; + + 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_x11_wrapper_idle, wrap); + } + + g_signal_connect(G_OBJECT(wrap), + "plug-removed", + G_CALLBACK(on_plug_removed), + NULL); + + g_signal_connect(G_OBJECT(wrap), + "size-allocate", + G_CALLBACK(suil_x11_on_size_allocate), + NULL); + + return 0; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl); + gtk_object_destroy(GTK_OBJECT(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; + + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER( + g_object_new(SUIL_TYPE_X11_WRAPPER, NULL)); + + wrapper->impl = wrap; + wrapper->resize.handle = wrap; + wrapper->resize.ui_resize = wrapper_resize; + + gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE); + gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE); + + const intptr_t parent_id = (intptr_t)gtk_plug_get_id(wrap->plug); + suil_add_feature(features, &n_features, LV2_UI__parent, (void*)parent_id); + 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; +} diff --git a/src/x11_in_gtk3.c b/src/x11_in_gtk3.c new file mode 100644 index 0000000..0c016c9 --- /dev/null +++ b/src/x11_in_gtk3.c @@ -0,0 +1,402 @@ +/* + Copyright 2011-2016 David Robillard <http://drobilla.net> + + 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 <string.h> + +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <gtk/gtkx.h> + +#include "./suil_internal.h" + +#include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#define SUIL_TYPE_X11_WRAPPER (suil_x11_wrapper_get_type()) +#define SUIL_X11_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_X11_WRAPPER, SuilX11Wrapper)) + +typedef struct _SuilX11Wrapper SuilX11Wrapper; +typedef struct _SuilX11WrapperClass SuilX11WrapperClass; + +struct _SuilX11Wrapper { + GtkSocket socket; + GtkPlug* plug; + SuilWrapper* wrapper; + SuilInstance* instance; + const LV2UI_Idle_Interface* idle_iface; + guint idle_id; + guint idle_ms; +}; + +struct _SuilX11WrapperClass { + GtkSocketClass parent_class; +}; + +GType suil_x11_wrapper_get_type(void); // Accessor for SUIL_TYPE_X11_WRAPPER + +G_DEFINE_TYPE(SuilX11Wrapper, suil_x11_wrapper, GTK_TYPE_SOCKET) + +/** + Check if 'swallowed' subwindow is known to the X server. + + Gdk/GTK can mark the window as realized, mapped and visible even though + there is no window-ID on the X server for it yet. Then, + suil_x11_on_size_allocate() will cause a "BadWinow" X error. +*/ +static bool +x_window_is_valid(SuilX11Wrapper* socket) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + Window root = 0; + Window parent = 0; + Window* children = NULL; + unsigned childcount = 0; + + XQueryTree(GDK_WINDOW_XDISPLAY(window), + GDK_WINDOW_XID(window), + &root, &parent, &children, &childcount); + for (unsigned i = 0; i < childcount; ++i) { + if (children[i] == (Window)socket->instance->ui_widget) { + XFree(children); + return true; + } + } + XFree(children); + return false; +} + +static gboolean +on_plug_removed(GtkSocket* sock, gpointer data) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(sock); + + 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; + } + + self->plug = NULL; + return TRUE; +} + +static void +suil_x11_wrapper_finalize(GObject* gobject) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(gobject); + + self->wrapper->impl = NULL; + + G_OBJECT_CLASS(suil_x11_wrapper_parent_class)->finalize(gobject); +} + +static void +suil_x11_wrapper_realize(GtkWidget* w) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w); + GtkSocket* const socket = GTK_SOCKET(w); + + if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize) { + GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize(w); + } + + gtk_socket_add_id(socket, gtk_plug_get_id(wrap->plug)); + + gtk_widget_realize(GTK_WIDGET(wrap->plug)); + + gtk_widget_set_sensitive(GTK_WIDGET(wrap->plug), TRUE); + gtk_widget_set_can_focus(GTK_WIDGET(wrap->plug), TRUE); + gtk_widget_grab_focus(GTK_WIDGET(wrap->plug)); +} + +static void +suil_x11_wrapper_show(GtkWidget* w) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w); + + if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show) { + GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show(w); + } + + gtk_widget_show(GTK_WIDGET(wrap->plug)); +} + +static gboolean +forward_key_event(SuilX11Wrapper* socket, + GdkEvent* gdk_event) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + GdkScreen* screen = gdk_visual_get_screen(gdk_window_get_visual(window)); + + Window target_window; + if (gdk_event->any.window == window) { + // Event sent up to the plug window, forward it up to the parent + GtkWidget* widget = GTK_WIDGET(socket->instance->host_widget); + GdkWindow* parent = gtk_widget_get_parent_window(widget); + if (parent) { + target_window = GDK_WINDOW_XID(parent); + } else { + return FALSE; // Wrapper is a top-level window, do nothing + } + } else { + // Event sent anywhere else, send to the plugin + target_window = (Window)socket->instance->ui_widget; + } + + XKeyEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease; + xev.root = GDK_WINDOW_XID(gdk_screen_get_root_window(screen)); + xev.window = target_window; + xev.subwindow = None; + xev.time = gdk_event->key.time; + xev.state = gdk_event->key.state; + xev.keycode = gdk_event->key.hardware_keycode; + + XSendEvent(GDK_WINDOW_XDISPLAY(window), + target_window, + False, + NoEventMask, + (XEvent*)&xev); + + return (gdk_event->any.window != window); +} + +static gboolean +idle_size_request(gpointer user_data) +{ + GtkWidget* w = GTK_WIDGET(user_data); + gtk_widget_queue_resize(w); + return FALSE; +} + +static void +forward_size_request(SuilX11Wrapper* socket, + GtkAllocation* allocation) +{ + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug)); + if (x_window_is_valid(socket)) { + // Calculate allocation size constrained to X11 limits for widget + int width = allocation->width; + int height = allocation->height; + XSizeHints hints; + memset(&hints, 0, sizeof(hints)); + XGetNormalHints(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + &hints); + if (hints.flags & PMaxSize) { + width = MIN(width, hints.max_width); + height = MIN(height, hints.max_height); + } + if (hints.flags & PMinSize) { + width = MAX(width, hints.min_width); + height = MAX(height, hints.min_height); + } + + // Resize widget window + XResizeWindow(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + width, height); + + // Get actual widget geometry + Window root; + int wx, wy; + unsigned int ww, wh; + unsigned int ignored; + XGetGeometry(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + &root, + &wx, &wy, &ww, &wh, + &ignored, &ignored); + + // Center widget in allocation + wx = (allocation->width - ww) / 2; + wy = (allocation->height - wh) / 2; + XMoveWindow(GDK_WINDOW_XDISPLAY(window), + (Window)socket->instance->ui_widget, + wx, wy); + } else { + /* Child has not been realized, so unable to resize now. + Queue an idle resize. */ + g_idle_add(idle_size_request, socket->plug); + } +} + +static gboolean +suil_x11_wrapper_key_event(GtkWidget* widget, + GdkEventKey* event) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget); + + if (self->plug) { + return forward_key_event(self, (GdkEvent*)event); + } + + return FALSE; +} + +static void +suil_x11_on_size_allocate(GtkWidget* widget, + GtkAllocation* a) +{ + SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget); + + if (self->plug + && gtk_widget_get_realized(widget) + && gtk_widget_get_mapped(widget) + && gtk_widget_get_visible(widget)) { + forward_size_request(self, a); + } +} + +static void +suil_x11_wrapper_class_init(SuilX11WrapperClass* klass) +{ + GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* const widget_class = GTK_WIDGET_CLASS(klass); + + gobject_class->finalize = suil_x11_wrapper_finalize; + widget_class->realize = suil_x11_wrapper_realize; + widget_class->show = suil_x11_wrapper_show; + widget_class->key_press_event = suil_x11_wrapper_key_event; + widget_class->key_release_event = suil_x11_wrapper_key_event; +} + +static void +suil_x11_wrapper_init(SuilX11Wrapper* self) +{ + self->plug = GTK_PLUG(gtk_plug_new(0)); + self->wrapper = NULL; + self->instance = NULL; + self->idle_iface = NULL; + self->idle_ms = 1000 / 30; // 30 Hz default +} + +static int +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_x11_wrapper_idle(void* data) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(data); + + wrap->idle_iface->idle(wrap->instance->handle); + + return TRUE; // Continue calling +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl); + + instance->host_widget = GTK_WIDGET(wrap); + wrap->wrapper = wrapper; + wrap->instance = instance; + + 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_x11_wrapper_idle, wrap); + } + + g_signal_connect(G_OBJECT(wrap), + "plug-removed", + G_CALLBACK(on_plug_removed), + NULL); + + g_signal_connect(G_OBJECT(wrap), + "size-allocate", + G_CALLBACK(suil_x11_on_size_allocate), + NULL); + + return 0; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilX11Wrapper* const wrap = SUIL_X11_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; + + SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER( + g_object_new(SUIL_TYPE_X11_WRAPPER, NULL)); + + wrapper->impl = wrap; + wrapper->resize.handle = wrap; + wrapper->resize.ui_resize = wrapper_resize; + + gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE); + gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE); + + const intptr_t parent_id = (intptr_t)gtk_plug_get_id(wrap->plug); + suil_add_feature(features, &n_features, LV2_UI__parent, (void*)parent_id); + 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; +} diff --git a/src/x11_in_qt4.cpp b/src/x11_in_qt4.cpp new file mode 100644 index 0000000..c21357d --- /dev/null +++ b/src/x11_in_qt4.cpp @@ -0,0 +1,151 @@ +/* + Copyright 2011-2015 David Robillard <http://drobilla.net> + + 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 <QX11EmbedContainer> +#include <QtEvents> +#undef signals + +#include "./suil_config.h" +#include "./suil_internal.h" + +extern "C" { + +typedef struct { + QX11EmbedContainer* host_widget; + QX11EmbedWidget* parent; +} SuilX11InQt4Wrapper; + +class SuilQX11Container : public QX11EmbedContainer +{ +public: + SuilQX11Container(SuilInstance* instance, + const LV2UI_Idle_Interface* idle_iface, + QX11EmbedWidget* widget) + : QX11EmbedContainer() + , _instance(instance) + , _idle_iface(idle_iface) + , _widget(widget) + , _ui_timer(0) + {} + +protected: + void showEvent(QShowEvent* event) { + if (_idle_iface && _ui_timer == 0) { + _ui_timer = this->startTimer(30); + _widget->embedInto(winId()); + resize(_widget->size()); + } + QX11EmbedContainer::showEvent(event); + } + + void timerEvent(QTimerEvent* event) { + if (event->timerId() == _ui_timer && _idle_iface) { + _idle_iface->idle(_instance->handle); + } + QX11EmbedContainer::timerEvent(event); + } + + void closeEvent(QCloseEvent* event) { + if (_ui_timer && _idle_iface) { + this->killTimer(_ui_timer); + _ui_timer = 0; + } + QX11EmbedContainer::closeEvent(event); + } + +private: + SuilInstance* const _instance; + const LV2UI_Idle_Interface* const _idle_iface; + QX11EmbedWidget* const _widget; + int _ui_timer; +}; + +static void +wrapper_free(SuilWrapper* wrapper) +{ + SuilX11InQt4Wrapper* impl = (SuilX11InQt4Wrapper*)wrapper->impl; + + if (impl->parent) { + delete impl->parent; + } + + if (impl->host_widget) { + delete impl->host_widget; + } + + free(impl); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + 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); + } + + SuilX11InQt4Wrapper* const impl = (SuilX11InQt4Wrapper*)wrapper->impl; + QX11EmbedWidget* const ew = impl->parent; + + impl->host_widget = new SuilQX11Container(instance, idle_iface, ew); + + instance->host_widget = impl->host_widget; + + return 0; +} + +static int +wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) +{ + QX11EmbedWidget* const ew = (QX11EmbedWidget*)handle; + ew->resize(width, height); + return 0; +} + +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) +{ + SuilX11InQt4Wrapper* const impl = (SuilX11InQt4Wrapper*) + calloc(1, sizeof(SuilX11InQt4Wrapper)); + + SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + + QX11EmbedWidget* const ew = new QX11EmbedWidget(); + + impl->parent = ew; + + wrapper->impl = impl; + wrapper->resize.handle = ew; + wrapper->resize.ui_resize = wrapper_resize; + + const intptr_t parent_id = (intptr_t)ew->winId(); + suil_add_feature(features, &n_features, LV2_UI__parent, (void*)parent_id); + suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize); + suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL); + + return wrapper; +} + +} // extern "C" diff --git a/src/x11_in_qt5.cpp b/src/x11_in_qt5.cpp new file mode 100644 index 0000000..be90323 --- /dev/null +++ b/src/x11_in_qt5.cpp @@ -0,0 +1,148 @@ +/* + Copyright 2011-2015 David Robillard <http://drobilla.net> + Copyright 2015 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 <QWidget> + +#include <QTimerEvent> +#include <QCloseEvent> + +#undef signals + +#include "./suil_config.h" +#include "./suil_internal.h" + +extern "C" { + +typedef struct { + QWidget* host_widget; + QWidget* parent; +} SuilX11InQt5Wrapper; + +class SuilQX11Widget : public QWidget +{ +public: + SuilQX11Widget(QWidget* parent, Qt::WindowFlags wflags) + : QWidget(parent, wflags) + , _instance(NULL) + , _idle_iface(NULL) + , _ui_timer(0) + {} + + void start_idle(SuilInstance* instance, + const LV2UI_Idle_Interface* idle_iface) { + _instance = instance; + _idle_iface = idle_iface; + if (_idle_iface && _ui_timer == 0) { + _ui_timer = this->startTimer(30); + } + } + +protected: + void timerEvent(QTimerEvent* event) { + if (event->timerId() == _ui_timer && _idle_iface) { + _idle_iface->idle(_instance->handle); + } + QWidget::timerEvent(event); + } + + void closeEvent(QCloseEvent* event) { + if (_ui_timer && _idle_iface) { + this->killTimer(_ui_timer); + _ui_timer = 0; + } + QWidget::closeEvent(event); + } + +private: + SuilInstance* _instance; + const LV2UI_Idle_Interface* _idle_iface; + int _ui_timer; +}; + +static void +wrapper_free(SuilWrapper* wrapper) +{ + SuilX11InQt5Wrapper* impl = (SuilX11InQt5Wrapper*)wrapper->impl; + + if (impl->host_widget) { + delete impl->host_widget; + } + + free(impl); +} + +static int +wrapper_wrap(SuilWrapper* wrapper, + SuilInstance* instance) +{ + SuilX11InQt5Wrapper* const impl = (SuilX11InQt5Wrapper*)wrapper->impl; + SuilQX11Widget* const ew = (SuilQX11Widget*)impl->parent; + + if (instance->descriptor->extension_data) { + const LV2UI_Idle_Interface* idle_iface + = (const LV2UI_Idle_Interface*) + instance->descriptor->extension_data(LV2_UI__idleInterface); + ew->start_idle(instance, idle_iface); + } + + impl->host_widget = ew; + + instance->host_widget = impl->host_widget; + + return 0; +} + +static int +wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) +{ + QWidget* const ew = (QWidget*)handle; + ew->resize(width, height); + return 0; +} + +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) +{ + SuilX11InQt5Wrapper* const impl = (SuilX11InQt5Wrapper*) + calloc(1, sizeof(SuilX11InQt5Wrapper)); + + SuilWrapper* wrapper = (SuilWrapper*)malloc(sizeof(SuilWrapper)); + wrapper->wrap = wrapper_wrap; + wrapper->free = wrapper_free; + + QWidget* const ew = new SuilQX11Widget(NULL, Qt::Window); + + impl->parent = ew; + + wrapper->impl = impl; + wrapper->resize.handle = ew; + wrapper->resize.ui_resize = wrapper_resize; + + void* parent_id = (void*)(intptr_t)ew->winId(); + suil_add_feature(features, &n_features, LV2_UI__parent, parent_id); + suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize); + suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL); + + return wrapper; +} + +} // extern "C" |