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