From fbdb13400bd5e5d77b36bcdf891927c7efc4d0cf Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 29 Oct 2014 01:14:26 +0000 Subject: Add Cocoa in Gtk wrapper (patch from Robin Gareus) (fix #988). git-svn-id: http://svn.drobilla.net/lad/trunk/suil@5475 a436a847-0d15-0410-975c-d299462d15a1 --- AUTHORS | 1 + NEWS | 3 +- src/cocoa_in_gtk2.mm | 411 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/instance.c | 6 + wscript | 20 +++ 5 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 src/cocoa_in_gtk2.mm diff --git a/AUTHORS b/AUTHORS index ab865a1..5ac0405 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,5 +10,6 @@ Contributors: * Idle interface fixes for X11 in Qt4 * Robin Gareus * Support for resizing X11 UIs in Gtk + * Cocoa in Gtk wrapper * Rui Nuno Capela * Fixes for X11 in Qt4 \ No newline at end of file diff --git a/NEWS b/NEWS index b4d8c2a..10d1204 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,9 @@ suil (0.8.3) unstable; * Configure based on compiler target OS for cross-compilation + * Add Cocoa in Gtk wrapper (patch from Robin Gareus) - -- David Robillard Sat, 04 Oct 2014 23:08:29 -0400 + -- David Robillard Tue, 28 Oct 2014 21:13:33 -0400 suil (0.8.2) stable; diff --git a/src/cocoa_in_gtk2.mm b/src/cocoa_in_gtk2.mm new file mode 100644 index 0000000..42d1cd2 --- /dev/null +++ b/src/cocoa_in_gtk2.mm @@ -0,0 +1,411 @@ +/* + Copyright 2011-2014 David Robillard + Copyright 2014 Robin Gareus + + 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 + +#include +#include + +#include "./suil_internal.h" + +#ifdef HAVE_LV2_1_6_0 +# include "lv2/lv2plug.in/ns/ext/options/options.h" +# include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#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; +#ifdef HAVE_LV2_1_6_0 + const LV2UI_Idle_Interface* idle_iface; + guint idle_id; + guint idle_ms; +#endif +}; + +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(frame); + requisition->height = CGRectGetHeight(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_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; +} + +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; +#ifdef HAVE_LV2_1_6_0 + self->idle_iface = NULL; + self->idle_ms = 1000 / 30; // 30 Hz default +#endif +} + +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; +} + +#ifdef HAVE_LV2_1_6_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 +} +#endif + +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 NSKeyDown: + [view keyDown:nsevent]; + return GDK_FILTER_REMOVE; + case NSKeyUp: + [view keyUp:nsevent]; + return GDK_FILTER_REMOVE; + case NSFlagsChanged: + [view flagsChanged:nsevent]; + return GDK_FILTER_REMOVE; + case NSMouseEntered: + [view mouseEntered:nsevent]; + return GDK_FILTER_REMOVE; + case NSMouseExited: + [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 NSMouseMoved: + [view mouseMoved:nsevent]; + break; + case NSLeftMouseDragged: + [view mouseDragged:nsevent]; + break; + case NSLeftMouseDown: + [view mouseDown:nsevent]; + break; + case NSLeftMouseUp: + [view mouseUp:nsevent]; + break; + case NSRightMouseDown: + [view rightMouseDown:nsevent]; + break; + case NSRightMouseUp: + [view rightMouseUp:nsevent]; + break; + case NSScrollWheel: + [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; + +#ifdef HAVE_LV2_1_6_0 + 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); + } +#endif + return 0; +} + +static void +wrapper_free(SuilWrapper* wrapper) +{ + if (wrapper->impl) { + SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(wrapper->impl); +#ifdef HAVE_LV2_1_6_0 + if (wrap->idle_id) { + g_source_remove(wrap->idle_id); + wrap->idle_id = 0; + } +#endif + 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*)malloc(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); + + suil_add_feature(features, &n_features, LV2_UI__parent, + gdk_quartz_window_get_nsview(window)); + + suil_add_feature(features, &n_features, LV2_UI__resize, + &wrapper->resize); +#ifdef HAVE_LV2_1_6_0 + 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; + } + } + } +#endif + return wrapper; +} + +} // extern "C" diff --git a/src/instance.c b/src/instance.c index 67cb8cb..6bc7a9c 100644 --- a/src/instance.c +++ b/src/instance.c @@ -26,6 +26,7 @@ #define QT4_UI_URI LV2_UI__Qt4UI #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 @@ -47,6 +48,8 @@ suil_ui_supported(const char* container_type_uri, && !strcmp(ui_type_uri, X11_UI_URI)) || (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, WIN_UI_URI)) + || (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, COCOA_UI_URI)) || (!strcmp(container_type_uri, QT4_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI))) { return SUIL_WRAPPING_EMBEDDED; @@ -79,6 +82,9 @@ open_wrapper(SuilHost* host, } else if (!strcmp(container_type_uri, GTK2_UI_URI) && !strcmp(ui_type_uri, WIN_UI_URI)) { module_name = "suil_win_in_gtk2"; + } else if (!strcmp(container_type_uri, GTK2_UI_URI) + && !strcmp(ui_type_uri, COCOA_UI_URI)) { + module_name = "suil_cocoa_in_gtk2"; } else if (!strcmp(container_type_uri, QT4_UI_URI) && !strcmp(ui_type_uri, X11_UI_URI)) { module_name = "suil_x11_in_qt4"; diff --git a/wscript b/wscript index fc28670..a565f8b 100644 --- a/wscript +++ b/wscript @@ -4,6 +4,7 @@ import subprocess import sys import waflib.Options as Options import waflib.extras.autowaf as autowaf +from waflib import TaskGen # Library and package version (UNIX style major, minor, micro) # major increment <=> incompatible changes @@ -71,6 +72,9 @@ def configure(conf): autowaf.check_pkg(conf, 'gtk+-x11-2.0', uselib_store='GTK2_X11', atleast_version='2.0.0', mandatory=False) + autowaf.check_pkg(conf, 'gtk+-quartz-2.0', uselib_store='GTK2_QUARTZ', + atleast_version='2.0.0', mandatory=False) + if not Options.options.no_qt: autowaf.check_pkg(conf, 'QtGui', uselib_store='QT4', atleast_version='4.0.0', mandatory=False) @@ -91,6 +95,9 @@ def configure(conf): module_ext += 'D' if conf.env.DEST_OS == 'win32': module_ext += '.dll' + elif conf.env.DEST_OS == 'darwin': + module_prefix = 'lib' + module_ext += '.dylib' else: module_prefix = 'lib' module_ext += '.so' @@ -114,6 +121,7 @@ def build(bld): # C Headers includedir = '${INCLUDEDIR}/suil-%s/suil' % SUIL_MAJOR_VERSION bld.install_files(includedir, bld.path.ant_glob('suil/*.h')) + TaskGen.task_gen.mappings['.mm'] = TaskGen.task_gen.mappings['.cc'] # Pkgconfig file autowaf.build_pc(bld, 'SUIL', SUIL_VERSION, SUIL_MAJOR_VERSION, [], @@ -195,6 +203,18 @@ def build(bld): linkflags = bld.env.NODELETE_FLAGS) autowaf.use_lib(bld, obj, 'GTK2 GTK2_X11 LV2 LV2_1_4_3') + if bld.is_defined('HAVE_GTK2') and bld.is_defined('HAVE_GTK2_QUARTZ'): + obj = bld(features = 'cxx cshlib', + source = 'src/cocoa_in_gtk2.mm', + target = 'suil_cocoa_in_gtk2', + includes = ['.'], + defines = ['SUIL_SHARED', 'SUIL_INTERNAL'], + install_path = module_dir, + cflags = cflags, + lib = modlib, + linkflags = ['-framework', 'Cocoa']) + autowaf.use_lib(bld, obj, 'GTK2 LV2 LV2_1_4_3') + if bld.is_defined('HAVE_GTK2') and bld.env.DEST_OS == 'win32': obj = bld(features = 'cxx cxxshlib', source = 'src/win_in_gtk2.cpp', -- cgit v1.2.1