1/* 2 Copyright 2011-2017 David Robillard <d@drobilla.net> 3 Copyright 2014 Robin Gareus <robin@gareus.org> 4 5 Permission to use, copy, modify, and/or distribute this software for any 6 purpose with or without fee is hereby granted, provided that the above 7 copyright notice and this permission notice appear in all copies. 8 9 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16*/ 17 18#include "suil_internal.h" 19 20#include "lv2/options/options.h" 21#include "lv2/urid/urid.h" 22 23#include <gdk/gdkquartz.h> 24#include <gtk/gtk.h> 25 26#include <string.h> 27 28#ifndef MAC_OS_X_VERSION_10_12 29# define MAC_OS_X_VERSION_10_12 101200 30#endif 31 32#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 33# define NSEventTypeFlagsChanged NSFlagsChanged 34# define NSEventTypeLeftMouseDown NSLeftMouseDown 35# define NSEventTypeLeftMouseDragged NSLeftMouseDragged 36# define NSEventTypeLeftMouseUp NSLeftMouseUp 37# define NSEventTypeMouseEntered NSMouseEntered 38# define NSEventTypeMouseExited NSMouseExited 39# define NSEventTypeMouseMoved NSMouseMoved 40# define NSEventTypeRightMouseDown NSRightMouseDown 41# define NSEventTypeRightMouseUp NSRightMouseUp 42# define NSEventTypeScrollWheel NSScrollWheel 43#endif 44 45extern "C" { 46 47#define SUIL_TYPE_COCOA_WRAPPER (suil_cocoa_wrapper_get_type()) 48#define SUIL_COCOA_WRAPPER(obj) \ 49 (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_COCOA_WRAPPER, SuilCocoaWrapper)) 50 51typedef struct _SuilCocoaWrapper SuilCocoaWrapper; 52typedef struct _SuilCocoaWrapperClass SuilCocoaWrapperClass; 53 54struct _SuilCocoaWrapper { 55 GtkWidget widget; 56 SuilWrapper* wrapper; 57 SuilInstance* instance; 58 59 GdkWindow* flt_win; 60 bool custom_size; 61 bool mapped; 62 int req_width; 63 int req_height; 64 int alo_width; 65 int alo_height; 66 67 const LV2UI_Idle_Interface* idle_iface; 68 guint idle_id; 69 guint idle_ms; 70}; 71 72struct _SuilCocoaWrapperClass { 73 GtkWidgetClass parent_class; 74}; 75 76GType 77suil_cocoa_wrapper_get_type(void); // Accessor for SUIL_TYPE_COCOA_WRAPPER 78 79G_DEFINE_TYPE(SuilCocoaWrapper, suil_cocoa_wrapper, GTK_TYPE_WIDGET) 80 81static void 82suil_cocoa_wrapper_finalize(GObject* gobject) 83{ 84 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(gobject); 85 86 self->wrapper->impl = NULL; 87 88 G_OBJECT_CLASS(suil_cocoa_wrapper_parent_class)->finalize(gobject); 89} 90 91static void 92suil_cocoa_realize(GtkWidget* widget) 93{ 94 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 95 g_return_if_fail(self != NULL); 96 97 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); 98 99 GdkWindowAttr attrs; 100 attrs.x = widget->allocation.x; 101 attrs.y = widget->allocation.y; 102 attrs.width = widget->allocation.width; 103 attrs.height = widget->allocation.height; 104 attrs.wclass = GDK_INPUT_OUTPUT; 105 attrs.window_type = GDK_WINDOW_CHILD; 106 attrs.visual = gtk_widget_get_visual(widget); 107 attrs.colormap = gtk_widget_get_colormap(widget); 108 attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK | 109 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | 110 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; 111 112 widget->window = 113 gdk_window_new(widget->parent->window, 114 &attrs, 115 GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP); 116 117 widget->style = gtk_style_attach(widget->style, widget->window); 118 119 gdk_window_set_user_data(widget->window, widget); 120 gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE); 121 gtk_widget_queue_resize(widget); 122} 123 124static void 125suil_cocoa_size_request(GtkWidget* widget, GtkRequisition* requisition) 126{ 127 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 128 if (self->custom_size) { 129 requisition->width = self->req_width; 130 requisition->height = self->req_height; 131 } else { 132 NSView* view = (NSView*)self->instance->ui_widget; 133 NSRect frame = [view frame]; 134 requisition->width = CGRectGetWidth(NSRectToCGRect(frame)); 135 requisition->height = CGRectGetHeight(NSRectToCGRect(frame)); 136 } 137} 138 139static void 140suil_cocoa_size_allocate(GtkWidget* widget, GtkAllocation* allocation) 141{ 142 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 143 self->alo_width = allocation->width; 144 self->alo_height = allocation->height; 145 146 if (!self->mapped) { 147 return; 148 } 149 150 gint xx, yy; 151 gtk_widget_translate_coordinates( 152 gtk_widget_get_parent(widget), widget, 0, 0, &xx, &yy); 153 154 NSView* view = (NSView*)self->instance->ui_widget; 155 [view setFrame:NSMakeRect(xx, yy, self->alo_width, self->alo_height)]; 156} 157 158static void 159suil_cocoa_map(GtkWidget* widget) 160{ 161 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 162 self->mapped = true; 163 164 if (self->alo_width == 0 || self->alo_height == 0) { 165 return; 166 } 167 168 gint xx, yy; 169 gtk_widget_translate_coordinates( 170 gtk_widget_get_parent(widget), widget, 0, 0, &xx, &yy); 171 172 NSView* view = (NSView*)self->instance->ui_widget; 173 [view setHidden:NO]; 174 [view setFrame:NSMakeRect(xx, yy, self->alo_width, self->alo_height)]; 175} 176 177static void 178suil_cocoa_unmap(GtkWidget* widget) 179{ 180 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 181 NSView* view = (NSView*)self->instance->ui_widget; 182 183 self->mapped = false; 184 [view setHidden:YES]; 185} 186 187static gboolean 188suil_cocoa_key_press(GtkWidget* widget, GdkEventKey* event) 189{ 190 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 191 if (!self->instance || !self->wrapper || !self->wrapper->impl) { 192 return FALSE; 193 } 194 195 NSEvent* nsevent = gdk_quartz_event_get_nsevent((GdkEvent*)event); 196 NSView* view = (NSView*)self->instance->ui_widget; 197 [view keyDown:nsevent]; 198 return TRUE; 199} 200 201static gboolean 202suil_cocoa_key_release(GtkWidget* widget, GdkEventKey* event) 203{ 204 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 205 if (!self->instance || !self->wrapper || !self->wrapper->impl) { 206 return FALSE; 207 } 208 209 NSEvent* nsevent = gdk_quartz_event_get_nsevent((GdkEvent*)event); 210 NSView* view = (NSView*)self->instance->ui_widget; 211 [view keyUp:nsevent]; 212 return TRUE; 213} 214 215static gboolean 216suil_cocoa_expose(GtkWidget* widget, GdkEventExpose* event) 217{ 218 SuilCocoaWrapper* const self = SUIL_COCOA_WRAPPER(widget); 219 NSView* view = (NSView*)self->instance->ui_widget; 220 [view drawRect:NSMakeRect(event->area.x, 221 event->area.y, 222 event->area.width, 223 event->area.height)]; 224 return TRUE; 225} 226 227static void 228suil_cocoa_wrapper_class_init(SuilCocoaWrapperClass* klass) 229{ 230 GObjectClass* const gobject_class = G_OBJECT_CLASS(klass); 231 GtkWidgetClass* const widget_class = (GtkWidgetClass*)(klass); 232 233 gobject_class->finalize = suil_cocoa_wrapper_finalize; 234 235 widget_class->realize = suil_cocoa_realize; 236 widget_class->expose_event = suil_cocoa_expose; 237 widget_class->size_request = suil_cocoa_size_request; 238 widget_class->size_allocate = suil_cocoa_size_allocate; 239 widget_class->map = suil_cocoa_map; 240 widget_class->unmap = suil_cocoa_unmap; 241 widget_class->key_press_event = suil_cocoa_key_press; 242 widget_class->key_release_event = suil_cocoa_key_release; 243} 244 245static void 246suil_cocoa_wrapper_init(SuilCocoaWrapper* self) 247{ 248 self->wrapper = NULL; 249 self->instance = NULL; 250 self->flt_win = NULL; 251 self->custom_size = false; 252 self->mapped = false; 253 self->req_width = 0; 254 self->req_height = 0; 255 self->alo_width = 0; 256 self->alo_height = 0; 257 self->idle_iface = NULL; 258 self->idle_ms = 1000 / 30; // 30 Hz default 259} 260 261static int 262wrapper_resize(LV2UI_Feature_Handle handle, int width, int height) 263{ 264 SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(handle); 265 266 wrap->req_width = width; 267 wrap->req_height = height; 268 wrap->custom_size = true; 269 270 gtk_widget_queue_resize(GTK_WIDGET(handle)); 271 272 return 0; 273} 274 275static gboolean 276suil_cocoa_wrapper_idle(void* data) 277{ 278 SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(data); 279 wrap->idle_iface->idle(wrap->instance->handle); 280 return TRUE; // Continue calling 281} 282 283static GdkFilterReturn 284event_filter(GdkXEvent* xevent, GdkEvent* event, gpointer data) 285{ 286 SuilCocoaWrapper* wrap = (SuilCocoaWrapper*)data; 287 if (!wrap->instance || !wrap->wrapper || !wrap->wrapper->impl) { 288 return GDK_FILTER_CONTINUE; 289 } 290 291 NSEvent* nsevent = (NSEvent*)xevent; 292 NSView* view = (NSView*)wrap->instance->ui_widget; 293 if (view && nsevent) { 294 switch ([nsevent type]) { 295 case NSEventTypeFlagsChanged: 296 [view flagsChanged:nsevent]; 297 return GDK_FILTER_REMOVE; 298 case NSEventTypeMouseEntered: 299 [view mouseEntered:nsevent]; 300 return GDK_FILTER_REMOVE; 301 case NSEventTypeMouseExited: 302 [view mouseExited:nsevent]; 303 return GDK_FILTER_REMOVE; 304 305 /* Explicitly pass though mouse events. Needed for mouse-drags leaving 306 the window, and mouse-up after that. */ 307 case NSEventTypeMouseMoved: 308 [view mouseMoved:nsevent]; 309 break; 310 case NSEventTypeLeftMouseDragged: 311 [view mouseDragged:nsevent]; 312 break; 313 case NSEventTypeLeftMouseDown: 314 [view mouseDown:nsevent]; 315 break; 316 case NSEventTypeLeftMouseUp: 317 [view mouseUp:nsevent]; 318 break; 319 case NSEventTypeRightMouseDown: 320 [view rightMouseDown:nsevent]; 321 break; 322 case NSEventTypeRightMouseUp: 323 [view rightMouseUp:nsevent]; 324 break; 325 case NSEventTypeScrollWheel: 326 [view scrollWheel:nsevent]; 327 break; 328 default: 329 break; 330 } 331 } 332 return GDK_FILTER_CONTINUE; 333} 334 335static int 336wrapper_wrap(SuilWrapper* wrapper, SuilInstance* instance) 337{ 338 SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(wrapper->impl); 339 340 instance->host_widget = GTK_WIDGET(wrap); 341 wrap->wrapper = wrapper; 342 wrap->instance = instance; 343 344 const LV2UI_Idle_Interface* idle_iface = NULL; 345 if (instance->descriptor->extension_data) { 346 idle_iface = 347 (const LV2UI_Idle_Interface*)instance->descriptor->extension_data( 348 LV2_UI__idleInterface); 349 } 350 351 if (idle_iface) { 352 wrap->idle_iface = idle_iface; 353 wrap->idle_id = g_timeout_add(wrap->idle_ms, suil_cocoa_wrapper_idle, wrap); 354 } 355 356 return 0; 357} 358 359static void 360wrapper_free(SuilWrapper* wrapper) 361{ 362 if (wrapper->impl) { 363 SuilCocoaWrapper* const wrap = SUIL_COCOA_WRAPPER(wrapper->impl); 364 if (wrap->idle_id) { 365 g_source_remove(wrap->idle_id); 366 wrap->idle_id = 0; 367 } 368 369 gdk_window_remove_filter(wrap->flt_win, event_filter, wrapper->impl); 370 gtk_object_destroy(GTK_OBJECT(wrap)); 371 } 372} 373 374SUIL_LIB_EXPORT 375SuilWrapper* 376suil_wrapper_new(SuilHost* host, 377 const char* host_type_uri, 378 const char* ui_type_uri, 379 LV2_Feature*** features, 380 unsigned n_features) 381{ 382 GtkWidget* parent = NULL; 383 for (unsigned i = 0; i < n_features; ++i) { 384 if (!strcmp((*features)[i]->URI, LV2_UI__parent)) { 385 parent = (GtkWidget*)(*features)[i]->data; 386 } 387 } 388 389 if (!GTK_CONTAINER(parent)) { 390 SUIL_ERRORF("No GtkContainer parent given for %s UI\n", ui_type_uri); 391 return NULL; 392 } 393 394 SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper)); 395 wrapper->wrap = wrapper_wrap; 396 wrapper->free = wrapper_free; 397 398 SuilCocoaWrapper* const wrap = 399 SUIL_COCOA_WRAPPER(g_object_new(SUIL_TYPE_COCOA_WRAPPER, NULL)); 400 401 wrapper->impl = wrap; 402 wrapper->resize.handle = wrap; 403 wrapper->resize.ui_resize = wrapper_resize; 404 405 gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(wrap)); 406 gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE); 407 gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE); 408 gtk_widget_realize(GTK_WIDGET(wrap)); 409 410 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap)); 411 wrap->flt_win = gtk_widget_get_window(parent); 412 gdk_window_add_filter(wrap->flt_win, event_filter, wrap); 413 414 NSView* parent_view = gdk_quartz_window_get_nsview(window); 415 suil_add_feature(features, &n_features, LV2_UI__parent, parent_view); 416 suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize); 417 suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL); 418 419 // Scan for URID map and options 420 LV2_URID_Map* map = NULL; 421 LV2_Options_Option* options = NULL; 422 for (LV2_Feature** f = *features; *f && (!map || !options); ++f) { 423 if (!strcmp((*f)->URI, LV2_OPTIONS__options)) { 424 options = (LV2_Options_Option*)(*f)->data; 425 } else if (!strcmp((*f)->URI, LV2_URID__map)) { 426 map = (LV2_URID_Map*)(*f)->data; 427 } 428 } 429 430 if (map && options) { 431 // Set UI update rate if given 432 LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate); 433 for (LV2_Options_Option* o = options; o->key; ++o) { 434 if (o->key == ui_updateRate) { 435 wrap->idle_ms = 1000.0f / *(const float*)o->value; 436 break; 437 } 438 } 439 } 440 441 return wrapper; 442} 443 444} // extern "C" 445