1/* 2 * Copyright (C) 2020 Robin Gareus <robin@gareus.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License along 15 * with this program; if not, write to the Free Software Foundation, Inc., 16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 */ 18 19#ifdef WAF_BUILD 20#include "gtk2ardour-config.h" 21#endif 22 23#include <gtkmm.h> 24#include <gtk/gtk.h> 25#include <gdk/gdkquartz.h> 26 27#include "pbd/convert.h" 28#include "pbd/error.h" 29#include "pbd/unwind.h" 30 31#include "ardour/plugin_insert.h" 32#include "ardour/vst3_plugin.h" 33 34#include "gtkmm2ext/gui_thread.h" 35 36#include "gui_thread.h" 37#include "vst3_nsview_plugin_ui.h" 38 39#include "pbd/i18n.h" 40 41using namespace PBD; 42using namespace ARDOUR; 43using namespace Steinberg; 44 45VST3PluginUI* 46create_mac_vst3_gui (boost::shared_ptr<PluginInsert> plugin_insert, Gtk::VBox** box) 47{ 48 VST3NSViewPluginUI* v = new VST3NSViewPluginUI (plugin_insert, boost::dynamic_pointer_cast<VST3Plugin> (plugin_insert->plugin())); 49 *box = v; 50 return v; 51} 52 53 54VST3NSViewPluginUI::VST3NSViewPluginUI (boost::shared_ptr<PluginInsert> pi, boost::shared_ptr<VST3Plugin> vst3) 55 : VST3PluginUI (pi, vst3) 56{ 57 pack_start (_gui_widget, true, true); 58 59 _gui_widget.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK); 60 _gui_widget.signal_realize().connect (mem_fun (this, &VST3NSViewPluginUI::view_realized)); 61 _gui_widget.signal_visibility_notify_event ().connect (mem_fun (this, &VST3NSViewPluginUI::view_visibility_notify)); 62 _gui_widget.signal_size_request ().connect (mem_fun (this, &VST3NSViewPluginUI::view_size_request)); 63 _gui_widget.signal_size_allocate ().connect (mem_fun (this, &VST3NSViewPluginUI::view_size_allocate)); 64 _gui_widget.signal_map ().connect (mem_fun (this, &VST3NSViewPluginUI::view_map)); 65 _gui_widget.signal_unmap ().connect (mem_fun (this, &VST3NSViewPluginUI::view_unmap)); 66 _gui_widget.signal_scroll_event ().connect (sigc::mem_fun (*this, &VST3NSViewPluginUI::forward_scroll_event), false); 67 68 //vst->LoadPresetProgram.connect (_program_connection, invalidator (*this), boost::bind (&VST3NSViewPluginUI::set_program, this), gui_context()); 69 70 _ns_view = [[NSView new] retain]; 71 72 IPlugView* view = _vst3->view (); 73 74 if (!view) { 75 printf ("FAILED TO get view.\n"); 76 return; 77 } 78 79 if (kResultOk != view->attached (reinterpret_cast<void*> (_ns_view), Steinberg::kPlatformTypeNSView)) { 80 printf ("FAILED TO ATTACH..\n"); 81 } 82 83 ViewRect rect; 84 if (view->getSize (&rect) == kResultOk) { 85 _req_width = rect.right - rect.left; 86 _req_height = rect.bottom - rect.top; 87 } 88 89 _gui_widget.show (); 90} 91 92VST3NSViewPluginUI::~VST3NSViewPluginUI () 93{ 94 assert (_view_realized); 95 _vst3->close_view (); 96 [_ns_view removeFromSuperview]; 97 [_ns_view release]; 98} 99 100void 101VST3NSViewPluginUI::view_size_request (GtkRequisition* requisition) 102{ 103 requisition->width = _req_width + 8; 104 requisition->height = _req_height + 6; 105} 106 107void 108VST3NSViewPluginUI::view_size_allocate (Gtk::Allocation& allocation) 109{ 110 IPlugView* view = _vst3->view (); 111 if (!view || !_view_realized) { 112 return; 113 } 114 115 gint xx = 0; 116 gint yy = 0; 117 gtk_widget_translate_coordinates( 118 GTK_WIDGET(_gui_widget.gobj()), 119 GTK_WIDGET(_gui_widget.get_parent()->gobj()), 120 0, 0, &xx, &yy); 121 122 PBD::Unwinder<bool> uw (_resize_in_progress, true); 123 124 ViewRect rect; 125 if (view->getSize (&rect) == kResultOk 126 && !(rect.right - rect.left == allocation.get_width () && rect.bottom - rect.top == allocation.get_height () && rect.left == xx && rect.top == yy)) 127 { 128 rect.left = xx; 129 rect.top = yy; 130 rect.right = rect.left + allocation.get_width (); 131 rect.bottom = rect.top + allocation.get_height (); 132#if 0 133 if (view->checkSizeConstraint (&rect) != kResultTrue) { 134 view->getSize (&rect); 135 } 136 allocation.set_width (rect.right - rect.left); 137 allocation.set_height (rect.bottom - rect.top); 138#endif 139 bool can_resize = true; 140#if 1 // quirks 141 /* Reason reports canResize() == kResultTrue, but crashes in onSize() 142 * reported to reasonstudios.com on 2020-11-19 */ 143 if (_vst3->unique_id() == "D75F65D1581F203A8D793B3C01583323" /* Reason Rack Plugin */) can_resize = false; 144 if (_vst3->unique_id() == "C64E2730B0614A6EAEFC432F9CF6151A" /* Reason Rack Plugin Effect */) can_resize = false; 145#endif 146 if (can_resize && view->canResize() == kResultTrue) { 147 view->onSize (&rect); 148 } 149 } 150 151 [_ns_view setFrame:NSMakeRect (xx, yy, allocation.get_width (), allocation.get_height ())]; 152 NSArray* subviews = [_ns_view subviews]; 153 for (unsigned long i = 0; i < [subviews count]; ++i) { 154 NSView* subview = [subviews objectAtIndex:i]; 155 [subview setFrame:NSMakeRect (0, 0, allocation.get_width (), allocation.get_height ())]; 156 break; /* only resize first subview */ 157 } 158} 159 160void 161VST3NSViewPluginUI::resize_callback (int width, int height) 162{ 163 //printf ("VST3NSViewPluginUI::resize_callback %d x %d\n", width, height); 164 IPlugView* view = _vst3->view (); 165 if (!view || _resize_in_progress) { 166 return; 167 } 168 if (view->canResize() == kResultTrue) { 169 gint xx, yy; 170 if (gtk_widget_translate_coordinates ( 171 GTK_WIDGET(_gui_widget.gobj()), 172 GTK_WIDGET(get_toplevel()->gobj()), 173 0, 0, &xx, &yy)) 174 { 175 get_window()->resize (width + xx, height + yy); 176 } 177 } else { 178 _req_width = width; 179 _req_height = height; 180 _gui_widget.queue_resize (); 181 } 182} 183 184void 185VST3NSViewPluginUI::view_realized () 186{ 187 NSWindow* win = get_nswindow (); 188 if (!win) { 189 printf ("NO WINDOW!\n"); 190 return; 191 } 192 193 [win setAutodisplay:1]; // turn off GTK stuff for this window 194 195 NSView* nsview = gdk_quartz_window_get_nsview (_gui_widget.get_window()->gobj()); 196 [nsview addSubview:_ns_view]; 197 _view_realized = true; 198 _gui_widget.queue_resize (); 199} 200 201bool 202VST3NSViewPluginUI::view_visibility_notify (GdkEventVisibility* ev) 203{ 204 return false; 205} 206 207void 208VST3NSViewPluginUI::view_map () 209{ 210 [_ns_view setHidden:0]; 211} 212 213void 214VST3NSViewPluginUI::view_unmap () 215{ 216 [_ns_view setHidden:1]; 217} 218 219 220bool 221VST3NSViewPluginUI::on_window_show (const std::string& /*title*/) 222{ 223 gtk_widget_realize (GTK_WIDGET(_gui_widget.gobj())); 224 show_all (); 225 _gui_widget.queue_resize (); 226 return true; 227} 228 229void 230VST3NSViewPluginUI::on_window_hide () 231{ 232 hide_all (); 233} 234 235void 236VST3NSViewPluginUI::forward_key_event (GdkEventKey* ev) 237{ 238 /* despite the VST3 spec mandating onKeyUp/Down, many 239 * plugins (notably JUCE) reply on platform callbacks. 240 */ 241 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev); 242 if (!nsevent) { 243 return; 244 } 245 if ([nsevent type] == NSKeyDown) { 246 [[[_ns_view window] firstResponder] keyDown:nsevent]; 247 } else if ([nsevent type] == NSKeyUp) { 248 [[[_ns_view window] firstResponder] keyUp:nsevent]; 249 } else if ([nsevent type] == NSFlagsChanged) { 250 [[[_ns_view window] firstResponder] flagsChanged:nsevent]; 251 } 252} 253 254void 255VST3NSViewPluginUI::grab_focus () 256{ 257 [_ns_view becomeFirstResponder]; 258} 259 260NSWindow* 261VST3NSViewPluginUI::get_nswindow () 262{ 263 Gtk::Container* toplevel = get_toplevel(); 264 if (!toplevel || !toplevel->is_toplevel()) { 265 error << _("VST3NSViewPluginUI: no top level window!") << endmsg; 266 return 0; 267 } 268 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj()); 269 270 if (!true_parent) { 271 error << _("VST3NSViewPluginUI: no top level window!") << endmsg; 272 return 0; 273 } 274 275 return true_parent; 276} 277