2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
5 #include <gtkmm/button.h>
6 #include <gdk/gdkquartz.h>
8 #include "pbd/convert.h"
11 #include "ardour/audio_unit.h"
12 #include "ardour/debug.h"
13 #include "ardour/plugin_insert.h"
15 #undef check // stupid gtk, stupid apple
17 #include <gtkmm2ext/utils.h>
18 #include <gtkmm2ext/window_proxy.h>
20 #include "au_pluginui.h"
21 #include "gui_thread.h"
22 #include "processor_box.h"
24 #include "CAAudioUnit.h"
25 #include "CAComponent.h"
27 #import <AudioUnit/AUCocoaUIView.h>
28 #import <CoreAudioKit/AUGenericView.h>
29 #import <objc/runtime.h>
30 #include <dispatch/dispatch.h>
36 #include "public_editor.h"
39 #include "gtk2ardour-config.h"
42 #define ArdourCloseComponent CloseComponent
44 #define ArdourCloseComponent AudioComponentInstanceDispose
46 using namespace ARDOUR;
48 using namespace Gtkmm2ext;
52 vector<string> AUPluginUI::automation_mode_strings;
53 int64_t AUPluginUI::last_timer = 0;
54 bool AUPluginUI::timer_needed = true;
55 CFRunLoopTimerRef AUPluginUI::cf_timer;
56 uint64_t AUPluginUI::timer_callbacks = 0;
57 uint64_t AUPluginUI::timer_out_of_range = 0;
59 static const gchar* _automation_mode_strings[] = {
68 dump_view_tree (NSView* view, int depth, int maxdepth)
70 NSArray* subviews = [view subviews];
71 unsigned long cnt = [subviews count];
74 NSView* su = [view superview];
76 NSRect sf = [su frame];
77 cerr << " PARENT view " << su << " @ " << sf.origin.x << ", " << sf.origin.y
78 << ' ' << sf.size.width << " x " << sf.size.height
83 for (int d = 0; d < depth; d++) {
86 NSRect frame = [view frame];
87 cerr << " view " << view << " @ " << frame.origin.x << ", " << frame.origin.y
88 << ' ' << frame.size.width << " x " << frame.size.height
91 if (depth >= maxdepth) {
94 for (unsigned long i = 0; i < cnt; ++i) {
95 NSView* subview = [subviews objectAtIndex:i];
96 dump_view_tree (subview, depth+1, maxdepth);
100 /* This deeply hacky block of code exists for a rather convoluted reason.
102 * The proximal reason is that there are plugins (such as XLN's Addictive Drums
103 * 2) which redraw their GUI/editor windows using a timer, and use a drawing
104 * technique that on Retina displays ends up calling arg32_image_mark_RGB32, a
105 * function that for some reason (probably byte-swapping or pixel-doubling) is
106 * many times slower than the function used on non-Retina displays.
108 * We are not the first people to discover the problem with
109 * arg32_image_mark_RGB32.
111 * Justin Fraenkel, the lead author of Reaper, wrote a very detailed account of
112 * the performance issues with arg32_image_mark_RGB32 here:
113 * http://www.1014.org/?article=516
115 * The problem was also seen by Robert O'Callahan (lead developer of rr, the
116 * reverse debugger) as far back as 2010:
117 * http://robert.ocallahan.org/2010/05/cglayer-performance-trap-with-isflipped_03.html
119 * In fact, it is so slow that the drawing takes up close to 100% of a single
120 * core, and the event loop that the drawing occurs in never sleeps or "idles".
122 * In AU hosts built directly on top of Cocoa, or some other toolkits, this
123 * isn't inherently a major problem - it just makes the entire GUI of the
126 * However, there is an additional problem for Ardour because GTK+ is built on
127 * top of the GDK/Quartz event loop integration. This integration is rather
128 * baroque, mostly because it was written at a time when CFRunLoop did not
129 * offer a way to wait for "input" from file descriptors (which arrived in OS X
130 * 10.5). As a result, it uses a hair-raising design involving an additional
131 * thread. This design has a major problem, which is that it effectively
132 * creates two nested run loops.
134 * The GTK+/GDK/glib one runs until it has nothing to do, at which time it
135 * calls a function to wait until there is something to do. On Linux or Windows
136 * that would involve some variant or relative of poll(2), which puts the
137 * process to sleep until there is something to do.
139 * On OS X, glib ends up calling [CFRunLoop waitForNextEventMatchingMask] which
140 * will eventually put the process to sleep, but won't do so until the
141 * CFRunLoop also has nothing to do. This includes (at least) a complete redraw
142 * cycle. If redrawing takes too long, and there are timers expired for another
143 * redraw (e.g. Addictive Drums 2, again), then the CFRunLoop will just start
144 * another redraw cycle after processing any events and other stuff.
146 * If the CFRunLoop stays busy, then it will never return to the glib
147 * level at all, thus stopping any further GTK+ level activity (events,
148 * drawing) from taking place. In short, the current (spring 2016) design of
149 * the GDK/Quartz event loop integration relies on the idea that the internal
150 * CFRunLoop will go idle, and totally breaks if this does not happen.
152 * So take a fully functional Ardour, add in XLN's Addictive Drums 2, and a
153 * Retina display, and Apple's ridiculously slow blitting code, and the
154 * CFRunLoop never goes idle. As soon as Addictive Drums starts drawing (over
155 * and over again), the GTK+ event loop stops receiving events and stops
158 * One fix for this was to run a nested GTK+ event loop iteration (or two)
159 * whenever a plugin window was redrawn. This works in the sense that the
160 * immediate issue (no GTK+ events or drawing) is fixed. But the recursive GTK+
161 * event loop causes its own (very subtle) problems too.
163 * This code takes a rather radical approach. We use Objective C's ability to
164 * swizzle object methods. Specifically, we replace [NSView displayIfNeeded]
165 * with our own version which will skip redraws of plugin windows if we tell it
166 * too. If we haven't done that, or if the redraw is of a non-plugin window,
167 * then we invoke the original displayIfNeeded method.
169 * After every 10 redraws of a given plugin GUI/editor window, we queue up a
170 * GTK/glib idle callback to measure the interval between those idle
171 * callbacks. We do this globally across all plugin windows, so if the callback
172 * is already queued, we don't requeue it.
174 * If the interval is longer than 40msec (a 25fps redraw rate), we set
175 * block_plugin_redraws to some number. Each successive call to our interposed
176 * displayIfNeeded method will (a) check this value and if non-zero (b) check
177 * if the call is for a plugin-related NSView/NSWindow. If it is, then we will
178 * skip the redisplay entirely, hopefully avoiding any calls to
179 * argb32_image_mark_RGB32 or any other slow drawing code, and thus allowing
180 * the CFRunLoop to go idle. If the value is zero or the call is for a
181 * non-plugin window, then we just invoke the "original" displayIfNeeded
184 * This hack adds a tiny bit of overhead onto redrawing of the entire
185 * application. But in the common case this consists of 1 conditional (the
186 * check on block_plugin_redraws, which will find it to be zero) and the
187 * invocation of the original method. Given how much work is typically done
188 * during drawing, this seems acceptable.
190 * The correct fix for this is to redesign the relationship between
191 * GTK+/GDK/glib so that a glib run loop is actually a CFRunLoop, with all
192 * GSources represented as CFRunLoopSources, without any nesting and without
193 * any additional thread. This is not a task to be undertaken lightly, and is
194 * certainly substantially more work than this was. It may never be possible to
195 * do that work in a way that could be integrated back into glib, because of
196 * the rather specific semantics and types of GSources, but it would almost
197 * certainly be possible to make it work for Ardour.
200 static IMP original_nsview_drawIfNeeded;
201 static std::vector<id> plugin_views;
202 static uint32_t block_plugin_redraws = 0;
203 static const uint32_t minimum_redraw_rate = 25; /* frames per second */
204 static const uint32_t block_plugin_redraw_count = 10; /* number of combined plugin redraws to block, if blocking */
206 static void add_plugin_view (id view)
208 if (plugin_views.empty()) {
209 AUPluginUI::start_cf_timer ();
212 plugin_views.push_back (view);
216 static void remove_plugin_view (id view)
218 std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
219 if (x != plugin_views.end()) {
220 plugin_views.erase (x);
222 if (plugin_views.empty()) {
223 AUPluginUI::stop_cf_timer ();
227 static void interposed_drawIfNeeded (id receiver, SEL selector, NSRect rect)
229 if (block_plugin_redraws && (find (plugin_views.begin(), plugin_views.end(), receiver) != plugin_views.end())) {
230 block_plugin_redraws--;
231 std::cerr << "Plugin redraw blocked\n";
232 /* YOU ... SHALL .... NOT ... DRAW!!!! */
235 (void) ((int (*)(id,SEL,NSRect)) original_nsview_drawIfNeeded) (receiver, selector, rect);
238 @implementation NSView (Tracking)
240 static dispatch_once_t once_token;
242 /* this swizzles NSView::displayIfNeeded and replaces it with
243 * interposed_drawIfNeeded(), which allows us to interpose and block
244 * the redrawing of plugin UIs when their redrawing behaviour
245 * is interfering with event loop behaviour.
248 dispatch_once (&once_token, ^{
249 Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
250 original_nsview_drawIfNeeded = method_setImplementation (target, (IMP) interposed_drawIfNeeded);
256 /* END OF THE PLUGIN REDRAW HACK */
258 @implementation NotificationObject
260 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
262 self = [ super init ];
265 plugin_ui = apluginui;
266 top_level_parent = tlp;
271 [[NSNotificationCenter defaultCenter]
273 selector:@selector(cocoaParentActivationHandler:)
274 name:NSWindowDidBecomeMainNotification
277 [[NSNotificationCenter defaultCenter]
279 selector:@selector(cocoaParentBecameKeyHandler:)
280 name:NSWindowDidBecomeKeyNotification
288 - (void)cocoaParentActivationHandler:(NSNotification *)notification
290 NSWindow* notification_window = (NSWindow *)[notification object];
292 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
293 if ([notification_window isMainWindow]) {
294 plugin_ui->activate();
296 plugin_ui->deactivate();
301 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
303 NSWindow* notification_window = (NSWindow *)[notification object];
305 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
306 if ([notification_window isKeyWindow]) {
307 plugin_ui->activate();
309 plugin_ui->deactivate();
314 - (void)auViewResized:(NSNotification *)notification
316 (void) notification; // stop complaints about unusued argument
317 plugin_ui->cocoa_view_resized();
322 @implementation LiveResizeNotificationObject
324 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
326 self = [ super init ];
328 plugin_ui = apluginui;
334 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
336 plugin_ui->start_live_resize ();
339 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
341 plugin_ui->end_live_resize ();
345 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
346 : PlugUIBase (insert)
347 , automation_mode_label (_("Automation"))
348 , preset_label (_("Presets"))
354 , in_live_resize (false)
355 , plugin_requested_resize (0)
360 if (automation_mode_strings.empty()) {
361 automation_mode_strings = I18N (_automation_mode_strings);
364 set_popdown_strings (automation_mode_selector, automation_mode_strings);
365 automation_mode_selector.set_active_text (automation_mode_strings.front());
367 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
368 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
369 throw failed_constructor ();
372 /* stuff some stuff into the top of the window */
374 HBox* smaller_hbox = manage (new HBox);
376 smaller_hbox->set_spacing (6);
377 smaller_hbox->pack_start (preset_label, false, false, 4);
378 smaller_hbox->pack_start (_preset_modified, false, false);
379 smaller_hbox->pack_start (_preset_combo, false, false);
380 smaller_hbox->pack_start (add_button, false, false);
382 /* Ardour does not currently allow to overwrite existing presets
383 * see save_property_list() in audio_unit.cc
385 smaller_hbox->pack_start (save_button, false, false);
388 /* one day these might be useful with an AU plugin, but not yet */
389 smaller_hbox->pack_start (automation_mode_label, false, false);
390 smaller_hbox->pack_start (automation_mode_selector, false, false);
392 smaller_hbox->pack_start (reset_button, false, false);
393 smaller_hbox->pack_start (bypass_button, false, true);
395 VBox* v1_box = manage (new VBox);
396 VBox* v2_box = manage (new VBox);
398 v1_box->pack_start (*smaller_hbox, false, true);
399 v2_box->pack_start (focus_button, false, true);
401 top_box.set_homogeneous (false);
402 top_box.set_spacing (6);
403 top_box.set_border_width (6);
405 top_box.pack_end (*v2_box, false, false);
406 top_box.pack_end (*v1_box, false, false);
409 pack_start (top_box, false, false);
410 pack_start (low_box, true, true);
412 preset_label.show ();
413 _preset_combo.show ();
414 automation_mode_label.show ();
415 automation_mode_selector.show ();
416 bypass_button.show ();
424 _activating_from_app = false;
431 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
433 if (test_cocoa_view_support()) {
434 create_cocoa_view ();
436 } else if (test_carbon_view_support()) {
437 create_carbon_view ();
440 create_cocoa_view ();
443 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
445 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
446 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
448 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
449 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
450 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
451 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
455 AUPluginUI::~AUPluginUI ()
458 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
461 if (_resize_notify) {
462 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
465 NSWindow* win = get_nswindow();
467 remove_plugin_view ([[win contentView] superview]);
472 [win removeChildWindow:cocoa_parent];
476 /* not parented, just overlaid on top of our window */
477 DisposeWindow (carbon_window);
482 ArdourCloseComponent (editView);
486 /* remove whatever we packed into low_box so that GTK doesn't
489 [au_view removeFromSuperview];
494 AUPluginUI::test_carbon_view_support ()
499 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
500 carbon_descriptor.componentSubType = 'gnrc';
501 carbon_descriptor.componentManufacturer = 'appl';
502 carbon_descriptor.componentFlags = 0;
503 carbon_descriptor.componentFlagsMask = 0;
507 // ask the AU for its first editor component
509 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
511 int nEditors = propertySize / sizeof(ComponentDescription);
512 ComponentDescription *editors = new ComponentDescription[nEditors];
513 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
515 // just pick the first one for now
516 carbon_descriptor = editors[0];
529 AUPluginUI::test_cocoa_view_support ()
532 Boolean isWritable = 0;
533 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
534 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
535 0, &dataSize, &isWritable);
537 return dataSize > 0 && err == noErr;
541 AUPluginUI::plugin_class_valid (Class pluginClass)
543 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
544 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
545 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
553 AUPluginUI::create_cocoa_view ()
555 bool wasAbleToLoadCustomView = false;
556 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
557 UInt32 numberOfClasses = 0;
560 NSString* factoryClassName = 0;
561 NSURL* CocoaViewBundlePath = NULL;
563 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
564 kAudioUnitProperty_CocoaUI,
565 kAudioUnitScope_Global,
570 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
572 // Does view have custom Cocoa UI?
574 if ((result == noErr) && (numberOfClasses > 0) ) {
576 DEBUG_TRACE(DEBUG::AudioUnits,
577 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
579 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
581 if(AudioUnitGetProperty(*au->get_au(),
582 kAudioUnitProperty_CocoaUI,
583 kAudioUnitScope_Global,
586 &dataSize) == noErr) {
588 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
590 // we only take the first view in this example.
591 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
593 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
594 [factoryClassName UTF8String], CocoaViewBundlePath));
598 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
600 if (cocoaViewInfo != NULL) {
601 free (cocoaViewInfo);
602 cocoaViewInfo = NULL;
607 // [A] Show custom UI if view has it
609 if (CocoaViewBundlePath && factoryClassName) {
610 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
612 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
614 if (viewBundle == NULL) {
615 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
618 Class factoryClass = [viewBundle classNamed:factoryClassName];
619 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
621 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
625 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
626 if (!plugin_class_valid (factoryClass)) {
627 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
631 id factory = [[[factoryClass alloc] init] autorelease];
632 if (factory == NULL) {
633 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
637 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
640 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
642 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
645 [CocoaViewBundlePath release];
648 for (i = 0; i < numberOfClasses; i++)
649 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
651 free (cocoaViewInfo);
653 wasAbleToLoadCustomView = true;
657 if (!wasAbleToLoadCustomView) {
658 // load generic Cocoa view
659 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
661 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
662 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
663 [(AUGenericView *)au_view setShowsExpertParameters:1];
666 // Get the initial size of the new AU View's frame
667 NSRect frame = [au_view frame];
668 req_width = frame.size.width;
669 req_height = frame.size.height;
671 resizable = [au_view autoresizingMask];
673 low_box.queue_resize ();
679 AUPluginUI::update_view_size ()
681 last_au_frame = [au_view frame];
686 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
688 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
692 AUPluginUI::cf_timer_callback ()
694 int64_t now = ARDOUR::get_microseconds ();
702 const int64_t usecs_slop = 7500; /* 7.5 msec */
704 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
706 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
707 timer_out_of_range++;
710 /* check timing roughly every second */
712 if ((timer_callbacks % minimum_redraw_rate) == 0) {
713 std::cerr << "OOR check: " << timer_out_of_range << std::endl;
714 if (timer_out_of_range > (minimum_redraw_rate / 4)) {
715 /* more than 25 % of the last second's worth of timers
716 have been late. Take action.
718 block_plugin_redraws = block_plugin_redraw_count;
719 std::cerr << "Timer too slow, block plugin redraws\n";
721 timer_out_of_range = 0;
728 AUPluginUI::start_cf_timer ()
734 CFTimeInterval interval = 1.0/25.0; /* secs => 40msec or 25fps */
736 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
737 CFAbsoluteTimeGetCurrent() + interval,
739 au_cf_timer_callback,
742 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
743 timer_needed = false;
747 AUPluginUI::stop_cf_timer ()
753 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
759 AUPluginUI::cocoa_view_resized ()
761 /* we can get here for two reasons:
763 1) the plugin window was resized by the user, a new size was
764 allocated to the window, ::update_view_size() was called, and we
765 explicitly/manually resized the AU NSView.
767 2) the plugin decided to resize itself (probably in response to user
768 action, but not in response to an actual window resize)
770 We only want to proceed with a window resizing in the second case.
773 if (in_live_resize) {
774 /* ::update_view_size() will be called at the right times and
775 * will update the view size. We don't need to anything while a
776 * live resize in underway.
781 if (plugin_requested_resize) {
782 /* we tried to change the plugin frame from inside this method
783 * (to adjust the origin), which changes the frame of the AU
784 * NSView, resulting in a reentrant call to the FrameDidChange
785 * handler (this method). Ignore this reentrant call.
787 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
791 plugin_requested_resize = 1;
793 ProcessorWindowProxy* wp = insert->window_proxy();
795 /* Once a plugin has requested a resize of its own window, do
796 * NOT save the window. The user may save state with the plugin
797 * editor expanded to show "extra detail" - the plugin will not
798 * refill this space when the editor is first
799 * instantiated. Leaving the window in the "too big" state
800 * cannot be recovered from.
802 * The window will be sized to fit the plugin's own request. Done.
804 wp->set_state_mask (WindowProxy::Position);
807 NSRect new_frame = [au_view frame];
809 /* from here on, we know that we've been called because the plugin
810 * decided to change the NSView frame itself.
813 /* step one: compute the change in the frame size.
816 float dy = new_frame.size.height - last_au_frame.size.height;
817 float dx = new_frame.size.width - last_au_frame.size.width;
819 NSWindow* window = get_nswindow ();
820 NSRect windowFrame= [window frame];
822 /* we want the top edge of the window to remain in the same place,
823 but the Cocoa/Quartz origin is at the lower left. So, when we make
824 the window larger, we will move it down, which means shifting the
825 origin toward (x,0). This will leave the top edge in the same place.
828 windowFrame.origin.y -= dy;
829 windowFrame.origin.x -= dx;
830 windowFrame.size.height += dy;
831 windowFrame.size.width += dx;
833 NSUInteger old_auto_resize = [au_view autoresizingMask];
835 /* Some stupid AU Views change the origin of the original AU View when
836 they are resized (I'm looking at you AUSampler). If the origin has
837 been moved, move it back.
840 if (last_au_frame.origin.x != new_frame.origin.x ||
841 last_au_frame.origin.y != new_frame.origin.y) {
842 new_frame.origin = last_au_frame.origin;
843 [au_view setFrame:new_frame];
844 /* also be sure to redraw the topbox because this can
847 top_box.queue_draw ();
850 /* We resize the window using Cocoa. We can't use GTK mechanisms
853 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
855 * "The host needs to be aware that changing the size of the window in
856 * response to the NSViewFrameDidChangeNotification can cause the view
857 * size to change depending on the autoresizing mask of the view. The
858 * host may need to cache the autoresizing mask of the view, set it to
859 * NSViewNotSizable, resize the window, and then reset the autoresizing
860 * mask of the view once the window has been sized."
864 [au_view setAutoresizingMask:NSViewNotSizable];
865 [window setFrame:windowFrame display:1];
866 [au_view setAutoresizingMask:old_auto_resize];
868 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
869 last_au_frame = new_frame;
870 req_width = new_frame.size.width;
871 req_height = new_frame.size.height;
873 plugin_requested_resize = 0;
877 AUPluginUI::create_carbon_view ()
881 ControlRef root_control;
883 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
885 OpenAComponent(editComponent, &editView);
887 error << _("AU Carbon view: cannot open AU Component") << endmsg;
891 Rect r = { 100, 100, 100, 100 };
892 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
893 kWindowCompositingAttribute|
894 kWindowNoShadowAttribute|
895 kWindowNoTitleBarAttribute);
897 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
898 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
899 ArdourCloseComponent (editView);
903 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
904 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
905 DisposeWindow (carbon_window);
906 ArdourCloseComponent (editView);
911 Float32Point location = { 0.0, 0.0 };
912 Float32Point size = { 0.0, 0.0 } ;
914 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
915 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
916 DisposeWindow (carbon_window);
917 ArdourCloseComponent (editView);
924 GetControlBounds(viewPane, &bounds);
925 size.x = bounds.right-bounds.left;
926 size.y = bounds.bottom-bounds.top;
928 req_width = (int) (size.x + 0.5);
929 req_height = (int) (size.y + 0.5);
931 SizeWindow (carbon_window, req_width, req_height, true);
932 low_box.set_size_request (req_width, req_height);
936 error << _("AU Carbon GUI is not supported.") << endmsg;
942 AUPluginUI::get_nswindow ()
944 Gtk::Container* toplevel = get_toplevel();
946 if (!toplevel || !toplevel->is_toplevel()) {
947 error << _("AUPluginUI: no top level window!") << endmsg;
951 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
954 error << _("AUPluginUI: no top level window!") << endmsg;
962 AUPluginUI::activate ()
965 ActivateWindow (carbon_window, TRUE);
970 AUPluginUI::deactivate ()
973 ActivateWindow (carbon_window, FALSE);
978 AUPluginUI::parent_carbon_window ()
981 NSWindow* win = get_nswindow ();
982 Rect windowStructureBoundsRect;
988 /* figure out where the cocoa parent window is in carbon-coordinate space, which
989 differs from both cocoa-coordinate space and GTK-coordinate space
992 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
994 /* compute how tall the title bar is, because we have to offset the position of the carbon window
998 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
999 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1001 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1003 int packing_extra = 6; // this is the total vertical packing in our top level window
1005 /* move into position, based on parent window position */
1006 MoveWindow (carbon_window,
1007 windowStructureBoundsRect.left,
1008 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1010 ShowWindow (carbon_window);
1012 // create the cocoa window for the carbon one and make it visible
1013 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1015 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1017 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1019 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1020 [win setAutodisplay:1]; // turn of GTK stuff for this window
1029 AUPluginUI::parent_cocoa_window ()
1031 NSWindow* win = get_nswindow ();
1037 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1039 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1040 [view addSubview:au_view];
1041 /* despite the fact that the documentation says that [NSWindow
1042 contentView] is the highest "accessible" NSView in an NSWindow, when
1043 the redraw cycle is executed, displayIfNeeded is actually executed
1044 on the parent of the contentView. To provide a marginal speedup when
1045 checking if a given redraw is for a plugin, use this "hidden" NSView
1046 to identify the plugin, so that we do not have to call [superview]
1047 every time in interposed_drawIfNeeded().
1049 add_plugin_view ([[win contentView] superview]);
1051 /* this moves the AU NSView down and over to provide a left-hand margin
1052 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1053 * keyboard focus control, bypass etc).
1057 gtk_widget_translate_coordinates(
1058 GTK_WIDGET(low_box.gobj()),
1059 GTK_WIDGET(low_box.get_parent()->gobj()),
1061 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1063 last_au_frame = [au_view frame];
1064 // watch for size changes of the view
1065 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1067 [[NSNotificationCenter defaultCenter] addObserver:_notify
1068 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1071 // catch notifications that live resizing is about to start
1073 #if HAVE_COCOA_LIVE_RESIZING
1074 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1076 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1077 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1080 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1081 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1084 /* No way before 10.6 to identify the start of a live resize (drag
1085 * resize) without subclassing NSView and overriding two of its
1086 * methods. Instead of that, we make the window non-resizable, thus
1087 * ending confusion about whether or not resizes are plugin or user
1088 * driven (they are always plugin-driven).
1091 Gtk::Container* toplevel = get_toplevel();
1096 if (toplevel && toplevel->is_toplevel()) {
1097 toplevel->size_request (req);
1098 toplevel->set_size_request (req.width, req.height);
1099 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1107 AUPluginUI::grab_focus()
1110 [au_view becomeFirstResponder];
1114 AUPluginUI::forward_key_event (GdkEventKey* ev)
1116 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1118 if (au_view && nsevent) {
1120 /* filter on nsevent type here because GDK massages FlagsChanged
1121 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1122 handle a FlagsChanged message as a keyDown or keyUp
1125 if ([nsevent type] == NSKeyDown) {
1126 [[[au_view window] firstResponder] keyDown:nsevent];
1127 } else if ([nsevent type] == NSKeyUp) {
1128 [[[au_view window] firstResponder] keyUp:nsevent];
1129 } else if ([nsevent type] == NSFlagsChanged) {
1130 [[[au_view window] firstResponder] flagsChanged:nsevent];
1136 AUPluginUI::on_realize ()
1138 VBox::on_realize ();
1140 /* our windows should not have that resize indicator */
1142 NSWindow* win = get_nswindow ();
1144 [win setShowsResizeIndicator:0];
1149 AUPluginUI::lower_box_realized ()
1152 parent_cocoa_window ();
1153 } else if (carbon_window) {
1154 parent_carbon_window ();
1159 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1162 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1163 ShowWindow (carbon_window);
1164 ActivateWindow (carbon_window, TRUE);
1172 AUPluginUI::lower_box_map ()
1174 [au_view setHidden:0];
1175 update_view_size ();
1179 AUPluginUI::lower_box_unmap ()
1181 [au_view setHidden:1];
1185 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1187 requisition->width = req_width;
1188 requisition->height = req_height;
1192 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1194 update_view_size ();
1198 AUPluginUI::on_window_hide ()
1201 if (carbon_window) {
1202 HideWindow (carbon_window);
1203 ActivateWindow (carbon_window, FALSE);
1209 NSArray* wins = [NSApp windows];
1210 for (uint32_t i = 0; i < [wins count]; i++) {
1211 id win = [wins objectAtIndex:i];
1217 AUPluginUI::on_window_show (const string& /*title*/)
1219 /* this is idempotent so just call it every time we show the window */
1221 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1226 if (carbon_window) {
1227 ShowWindow (carbon_window);
1228 ActivateWindow (carbon_window, TRUE);
1236 AUPluginUI::start_updating (GdkEventAny*)
1242 AUPluginUI::stop_updating (GdkEventAny*)
1248 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1250 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1256 AUPluginUI::start_live_resize ()
1258 in_live_resize = true;
1262 AUPluginUI::end_live_resize ()
1264 in_live_resize = false;