summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--NEWS3
-rw-r--r--src/cocoa_in_gtk2.mm411
-rw-r--r--src/instance.c6
-rw-r--r--wscript20
5 files changed, 440 insertions, 1 deletions
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 <robin@gareus.org>
* 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 <d@drobilla.net> Sat, 04 Oct 2014 23:08:29 -0400
+ -- David Robillard <d@drobilla.net> 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 <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"
+
+#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',