1 /* Calf DSP Library utility application.
2 * DSSI GUI application.
3 * Copyright (C) 2007 Krzysztof Foltman
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
19 */
20 #include <sys/wait.h>
21 #include "config.h"
22 #include <calf/gui.h>
23 #include <calf/giface.h>
24 #include <calf/lv2_atom.h>
25 #include <calf/lv2_data_access.h>
26 #include <calf/lv2_options.h>
27 #include <calf/lv2_ui.h>
28 #include <calf/lv2_urid.h>
29 #include <calf/lv2_external_ui.h>
30 #include <calf/lv2helpers.h>
31 #include <calf/utils.h>
32 #include <glib.h>
33
34 using namespace std;
35 using namespace calf_plugins;
36 using namespace calf_utils;
37
38 struct LV2_Calf_Descriptor {
39 plugin_ctl_iface *(*get_pci)(LV2_Handle Instance);
40 };
41
42 /// Temporary assignment to a slot in vector<bool>
43 typedef scope_assign<bool, vector<bool>::reference> TempSendSetter;
44
45 /// Common data and functions for GTK+ GUI and External GUI
46 struct plugin_proxy_base
47 {
48 const plugin_metadata_iface *plugin_metadata;
49 LV2UI_Write_Function write_function;
50 LV2UI_Controller controller;
51
52 // Values extracted from the Features array from the host
53
54 /// Handle to the plugin instance
55 LV2_Handle instance_handle;
56 /// Data access feature instance
57 LV2_Extension_Data_Feature *data_access;
58 /// URID map feature
59 const LV2_URID_Map *urid_map;
60 /// External UI host feature (must be set when instantiating external UI plugins)
61 lv2_external_ui_host *ext_ui_host;
62 bool atom_present;
63 uint32_t property_type, string_type, event_transfer;
64
65 /// Instance pointer - usually NULL unless the host supports instance-access extension
66 plugin_ctl_iface *instance;
67 /// If true, a given parameter (not port) may be sent to host - it is blocked when the parameter is written to by the host
68 vector<bool> sends;
69 /// Map of parameter name to parameter index (used for mapping configure values to string ports)
70 map<string, int> params_by_name;
71 /// Values of parameters (float control ports)
72 vector<float> params;
73 /// Number of parameters (non-audio ports)
74 int param_count;
75 /// Number of the first parameter port
76 int param_offset;
77 /// Signal handler for main widget destroyed
78 gulong widget_destroyed_signal;
79 /// Signal handler for external window destroyed
80 gulong window_destroyed_signal;
81
82 plugin_proxy_base(const plugin_metadata_iface *metadata, LV2UI_Write_Function wf, LV2UI_Controller c, const LV2_Feature* const* features);
83
84 /// Send a float value to a control port in the host
85 void send_float_to_host(int param_no, float value);
86
87 /// Send a string value to a string port in the host, by name (configure-like mechanism)
88 char *configure(const char *key, const char *value);
89
90 /// Obtain the list of variables from the plugin
91 void send_configures(send_configure_iface *sci);
92
93 /// Enable sending to host for all ports
94 void enable_all_sends();
95
96 /// Obtain instance pointers
97 void resolve_instance();
98
99 /// Obtain line graph interface if available
100 const line_graph_iface *get_line_graph_iface() const;
101
102 /// Obtain phase graph interface if available
103 const phase_graph_iface *get_phase_graph_iface() const;
104
105 /// Map an URI to an integer value using a given URID map
106 uint32_t map_urid(const char *uri);
107
108
109 };
110
plugin_proxy_base(const plugin_metadata_iface * metadata,LV2UI_Write_Function wf,LV2UI_Controller c,const LV2_Feature * const * features)111 plugin_proxy_base::plugin_proxy_base(const plugin_metadata_iface *metadata, LV2UI_Write_Function wf, LV2UI_Controller c, const LV2_Feature* const* features)
112 : instance_handle(NULL),
113 data_access(NULL),
114 urid_map(NULL),
115 ext_ui_host(NULL),
116 instance(NULL)
117 {
118 plugin_metadata = metadata;
119
120 write_function = wf;
121 controller = c;
122
123 atom_present = true; // XXXKF
124
125 param_count = metadata->get_param_count();
126 param_offset = metadata->get_param_port_offset();
127 widget_destroyed_signal = 0;
128 window_destroyed_signal = 0;
129
130 /// Block all updates until GUI is ready
131 sends.resize(param_count, false);
132 params.resize(param_count);
133 for (int i = 0; i < param_count; i++)
134 {
135 const parameter_properties *pp = metadata->get_param_props(i);
136 params_by_name[pp->short_name] = i;
137 params[i] = pp->def_value;
138 }
139 for (int i = 0; features[i]; i++)
140 {
141 if (!strcmp(features[i]->URI, "http://lv2plug.in/ns/ext/instance-access"))
142 {
143 instance_handle = features[i]->data;
144 }
145 else if (!strcmp(features[i]->URI, "http://lv2plug.in/ns/ext/data-access"))
146 {
147 data_access = (LV2_Extension_Data_Feature *)features[i]->data;
148 }
149 else if (!strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI))
150 {
151 ext_ui_host = (lv2_external_ui_host *)features[i]->data;
152 }
153 }
154 resolve_instance();
155 }
156
send_float_to_host(int param_no,float value)157 void plugin_proxy_base::send_float_to_host(int param_no, float value)
158 {
159 params[param_no] = value;
160 if (sends[param_no]) {
161 TempSendSetter _a_(sends[param_no], false);
162 write_function(controller, param_no + param_offset, sizeof(float), 0, ¶ms[param_no]);
163 }
164 }
165
resolve_instance()166 void plugin_proxy_base::resolve_instance()
167 {
168 fprintf(stderr, "CALF DEBUG: instance %p data %p\n", instance_handle, data_access);
169 if (instance_handle && data_access)
170 {
171 LV2_Calf_Descriptor *calf = (LV2_Calf_Descriptor *)(*data_access->data_access)("http://foltman.com/ns/calf-plugin-instance");
172 fprintf(stderr, "CALF DEBUG: calf %p cpi %p\n", calf, calf ? calf->get_pci : NULL);
173 if (calf && calf->get_pci)
174 instance = calf->get_pci(instance_handle);
175 }
176 }
177
map_urid(const char * uri)178 uint32_t plugin_proxy_base::map_urid(const char *uri)
179 {
180 if (!urid_map)
181 return 0;
182 return urid_map->map(urid_map->handle, uri);
183 }
184
get_line_graph_iface() const185 const line_graph_iface *plugin_proxy_base::get_line_graph_iface() const
186 {
187 if (instance)
188 return instance->get_line_graph_iface();
189 return NULL;
190 }
191
get_phase_graph_iface() const192 const phase_graph_iface *plugin_proxy_base::get_phase_graph_iface() const
193 {
194 if (instance)
195 return instance->get_phase_graph_iface();
196 return NULL;
197 }
198
configure(const char * key,const char * value)199 char *plugin_proxy_base::configure(const char *key, const char *value)
200 {
201 if (atom_present && event_transfer && string_type && property_type)
202 {
203 std::string pred = std::string("urn:calf:") + key;
204 uint32_t ss = strlen(value);
205 uint32_t ts = ss + 1 + sizeof(LV2_Atom_Property);
206 char *temp = new char[ts];
207 LV2_Atom_Property *prop = (LV2_Atom_Property *)temp;
208 prop->atom.type = property_type;
209 prop->atom.size = ts - sizeof(LV2_Atom);
210 prop->body.key = map_urid(pred.c_str());
211 prop->body.context = 0;
212 prop->body.value.size = ss + 1;
213 prop->body.value.type = string_type;
214 memcpy(temp + sizeof(LV2_Atom_Property), value, ss + 1);
215 write_function(controller, param_count + param_offset, ts, event_transfer, temp);
216 delete []temp;
217 return NULL;
218 }
219 else if (instance)
220 return instance->configure(key, value);
221 else
222 return strdup("Configuration not available because of lack of instance-access/data-access");
223 }
224
send_configures(send_configure_iface * sci)225 void plugin_proxy_base::send_configures(send_configure_iface *sci)
226 {
227 if (atom_present && event_transfer && string_type && property_type)
228 {
229 struct event_content
230 {
231 LV2_Atom_String str;
232 char buf[8];
233 } ec;
234 ec.str.atom.type = string_type;
235 ec.str.atom.size = 2;
236 strcpy(ec.buf, "?");
237 write_function(controller, param_count + param_offset, sizeof(LV2_Atom_String) + 2, event_transfer, &ec);
238 }
239 else if (instance)
240 {
241 fprintf(stderr, "Send configures...\n");
242 instance->send_configures(sci);
243 }
244 else
245 fprintf(stderr, "Configuration not available because of lack of instance-access/data-access\n");
246 }
247
enable_all_sends()248 void plugin_proxy_base::enable_all_sends()
249 {
250 sends.clear();
251 sends.resize(param_count, true);
252 }
253
254 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
255
256 /// Plugin controller that uses LV2 host with help of instance/data access to remotely
257 /// control a plugin from the GUI
258 struct lv2_plugin_proxy: public plugin_ctl_iface, public plugin_proxy_base, public gui_environment
259 {
260 /// Plugin GTK+ GUI object pointer
261 plugin_gui *gui;
262 /// Glib source ID for update timer
263 int source_id;
264
lv2_plugin_proxylv2_plugin_proxy265 lv2_plugin_proxy(const plugin_metadata_iface *md, LV2UI_Write_Function wf, LV2UI_Controller c, const LV2_Feature* const* f)
266 : plugin_proxy_base(md, wf, c, f)
267 {
268 gui = NULL;
269 source_id = 0;
270 if (instance)
271 {
272 conditions.insert("directlink");
273 conditions.insert("configure");
274 }
275 conditions.insert("lv2gui");
276 }
277
get_param_valuelv2_plugin_proxy278 virtual float get_param_value(int param_no) {
279 if (param_no < 0 || param_no >= param_count)
280 return 0;
281 return params[param_no];
282 }
283
set_param_valuelv2_plugin_proxy284 virtual void set_param_value(int param_no, float value) {
285 if (param_no < 0 || param_no >= param_count)
286 return;
287 send_float_to_host(param_no, value);
288 }
289
activate_presetlv2_plugin_proxy290 virtual bool activate_preset(int bank, int program)
291 {
292 return false;
293 }
294
295 /// Override for a method in plugin_ctl_iface - trivial delegation to base class
configurelv2_plugin_proxy296 virtual char *configure(const char *key, const char *value) { return plugin_proxy_base::configure(key, value); }
297
get_levellv2_plugin_proxy298 virtual float get_level(unsigned int port) { return 0.f; }
executelv2_plugin_proxy299 virtual void execute(int command_no) { assert(0); }
send_configureslv2_plugin_proxy300 virtual void send_configures(send_configure_iface *sci)
301 {
302 plugin_proxy_base::send_configures(sci);
303 }
send_status_updateslv2_plugin_proxy304 virtual int send_status_updates(send_updates_iface *sui, int last_serial)
305 {
306 if (instance)
307 return instance->send_status_updates(sui, last_serial);
308 else // no status updates because of lack of instance-access/data-access
309 return 0;
310 }
get_metadata_ifacelv2_plugin_proxy311 virtual const plugin_metadata_iface *get_metadata_iface() const { return plugin_metadata; }
312 /// Override for a method in plugin_ctl_iface - trivial delegation to base class
get_line_graph_ifacelv2_plugin_proxy313 virtual const line_graph_iface *get_line_graph_iface() const { return plugin_proxy_base::get_line_graph_iface(); }
314
315 /// Override for a method in plugin_ctl_iface - trivial delegation to base class
get_phase_graph_ifacelv2_plugin_proxy316 virtual const phase_graph_iface *get_phase_graph_iface() const { return plugin_proxy_base::get_phase_graph_iface(); }
317 };
318
plugin_on_idle(void * data)319 static gboolean plugin_on_idle(void *data)
320 {
321 plugin_gui *self = (plugin_gui *)data;
322 if (self->optwidget) {
323 self->on_idle();
324 return TRUE;
325 } else {
326 return FALSE;
327 }
328 }
329
on_gui_widget_destroy(GtkWidget *,gpointer data)330 static void on_gui_widget_destroy(GtkWidget*, gpointer data)
331 {
332 plugin_gui *gui = (plugin_gui *)data;
333 // Remove the reference to the widget, so that it's not being manipulated
334 // after it's been destroyed
335 gui->optwidget = NULL;
336 }
337
gui_instantiate(const struct _LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,LV2UI_Widget * widget,const LV2_Feature * const * features)338 LV2UI_Handle gui_instantiate(const struct _LV2UI_Descriptor* descriptor,
339 const char* plugin_uri,
340 const char* bundle_path,
341 LV2UI_Write_Function write_function,
342 LV2UI_Controller controller,
343 LV2UI_Widget* widget,
344 const LV2_Feature* const* features)
345 {
346 static int argc = 0;
347 gtk_init(&argc, NULL);
348
349 const plugin_metadata_iface *md = plugin_registry::instance().get_by_uri(plugin_uri);
350 if (!md)
351 return NULL;
352 lv2_plugin_proxy *proxy = new lv2_plugin_proxy(md, write_function, controller, features);
353 if (!proxy)
354 return NULL;
355
356 plugin_gui_window *window = new plugin_gui_window(proxy, NULL);
357 plugin_gui *gui = new plugin_gui(window);
358
359 const char *xml = proxy->plugin_metadata->get_gui_xml("gui");
360 assert(xml);
361 gui->optwidget = gui->create_from_xml(proxy, xml);
362 proxy->enable_all_sends();
363 if (gui->optwidget)
364 {
365 GtkWidget *decoTable = window->decorate(gui->optwidget);
366 GtkWidget *eventbox = gtk_event_box_new();
367 gtk_widget_set_name( GTK_WIDGET(eventbox), "Calf-Plugin" );
368 gtk_container_add( GTK_CONTAINER(eventbox), decoTable );
369 gtk_widget_show_all(eventbox);
370 gui->optwidget = eventbox;
371 proxy->source_id = g_timeout_add_full(G_PRIORITY_LOW, 1000/30, plugin_on_idle, gui, NULL); // 30 fps should be enough for everybody
372 proxy->widget_destroyed_signal = g_signal_connect(G_OBJECT(gui->optwidget), "destroy", G_CALLBACK(on_gui_widget_destroy), (gpointer)gui);
373 }
374 std::string rcf = PKGLIBDIR "/styles/" + proxy->get_config()->style + "/gtk.rc";
375 gtk_rc_parse(rcf.c_str());
376 window->show_rack_ears(proxy->get_config()->rack_ears);
377
378 *(GtkWidget **)(widget) = gui->optwidget;
379
380 // find window title
381 const LV2_Options_Option* options = NULL;
382 const LV2_URID_Map* uridMap = NULL;
383
384 for (int i=0; features[i]; i++)
385 {
386 if (!strcmp(features[i]->URI, LV2_OPTIONS__options))
387 options = (const LV2_Options_Option*)features[i]->data;
388 else if (!strcmp(features[i]->URI, LV2_URID_MAP_URI))
389 uridMap = (const LV2_URID_Map*)features[i]->data;
390 }
391
392 if (!options || !uridMap)
393 return (LV2UI_Handle)gui;
394
395 const uint32_t uridWindowTitle = uridMap->map(uridMap->handle, LV2_UI__windowTitle);
396 proxy->string_type = uridMap->map(uridMap->handle, LV2_ATOM__String);
397 proxy->property_type = uridMap->map(uridMap->handle, LV2_ATOM__Property);
398 proxy->event_transfer = uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer);
399 proxy->urid_map = uridMap;
400
401 proxy->send_configures(gui);
402
403 if (!uridWindowTitle)
404 return (LV2UI_Handle)gui;
405
406 for (int i=0; options[i].key; i++)
407 {
408 if (options[i].key == uridWindowTitle)
409 {
410 const char* windowTitle = (const char*)options[i].value;
411 gui->opttitle = strdup(windowTitle);
412 break;
413 }
414 }
415
416 return (LV2UI_Handle)gui;
417 }
418
gui_cleanup(LV2UI_Handle handle)419 void gui_cleanup(LV2UI_Handle handle)
420 {
421 plugin_gui *gui = (plugin_gui *)handle;
422 lv2_plugin_proxy *proxy = dynamic_cast<lv2_plugin_proxy *>(gui->plugin);
423 if (proxy->source_id)
424 g_source_remove(proxy->source_id);
425 // If the widget still exists, remove the handler
426 if (gui->optwidget)
427 {
428 g_signal_handler_disconnect(gui->optwidget, proxy->widget_destroyed_signal);
429 proxy->widget_destroyed_signal = 0;
430 }
431 gui->destroy_child_widgets(gui->optwidget);
432 gui->optwidget = NULL;
433
434 if (gui->opttitle)
435 {
436 free((void*)gui->opttitle);
437
438 // idle is no longer called after this, so make sure all events are handled now
439 while (gtk_events_pending())
440 gtk_main_iteration();
441 }
442
443 delete gui;
444 }
445
gui_port_event(LV2UI_Handle handle,uint32_t port,uint32_t buffer_size,uint32_t format,const void * buffer)446 void gui_port_event(LV2UI_Handle handle, uint32_t port, uint32_t buffer_size, uint32_t format, const void *buffer)
447 {
448 plugin_gui *gui = (plugin_gui *)handle;
449 if (gui->optclosed)
450 return;
451 lv2_plugin_proxy *proxy = dynamic_cast<lv2_plugin_proxy *>(gui->plugin);
452 assert(proxy);
453 float v = *(float *)buffer;
454 int param = port - proxy->plugin_metadata->get_param_port_offset();
455 if (param < 0 || param >= proxy->plugin_metadata->get_param_count())
456 {
457 if (format == proxy->event_transfer)
458 {
459 LV2_Atom *atom = (LV2_Atom *)buffer;
460 if (atom->type == proxy->string_type)
461 printf("Param %d string %s\n", param, (char *)LV2_ATOM_CONTENTS(LV2_Atom_String, atom));
462 else if (atom->type == proxy->property_type)
463 {
464 LV2_Atom_Property_Body *prop = (LV2_Atom_Property_Body *)LV2_ATOM_BODY(atom);
465 printf("Param %d key %d string %s\n", param, prop->key, (const char *)LV2_ATOM_CONTENTS(LV2_Atom_Property, atom));
466 }
467 else
468 printf("Param %d type %d\n", param, atom->type);
469 }
470 return;
471 }
472 if (!proxy->sends[param])
473 return;
474 if (fabs(gui->plugin->get_param_value(param) - v) < 0.00001)
475 return;
476 {
477 TempSendSetter _a_(proxy->sends[param], false);
478 gui->set_param_value(param, v);
479 }
480 }
481
gui_destroy(GtkWidget *,gpointer data)482 void gui_destroy(GtkWidget*, gpointer data)
483 {
484 plugin_gui *gui = (plugin_gui *)data;
485
486 gui->optclosed = true;
487 gui->optwindow = NULL;
488 }
489
gui_show(LV2UI_Handle handle)490 int gui_show(LV2UI_Handle handle)
491 {
492 plugin_gui *gui = (plugin_gui *)handle;
493 lv2_plugin_proxy *proxy = dynamic_cast<lv2_plugin_proxy *>(gui->plugin);
494
495 if (! gui->optwindow)
496 {
497 gui->optwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
498 proxy->window_destroyed_signal = g_signal_connect(G_OBJECT(gui->optwindow), "destroy", G_CALLBACK(gui_destroy), (gpointer)gui);
499
500 if (gui->optwidget)
501 gtk_container_add(GTK_CONTAINER(gui->optwindow), gui->optwidget);
502
503 if (gui->opttitle)
504 gtk_window_set_title(GTK_WINDOW(gui->optwindow), gui->opttitle);
505
506 gtk_window_set_resizable(GTK_WINDOW(gui->optwindow), false);
507 }
508
509 gtk_widget_show_all(gui->optwindow);
510 gtk_window_present(GTK_WINDOW(gui->optwindow));
511
512 return 0;
513 }
514
gui_hide(LV2UI_Handle handle)515 int gui_hide(LV2UI_Handle handle)
516 {
517 plugin_gui *gui = (plugin_gui *)handle;
518 lv2_plugin_proxy *proxy = dynamic_cast<lv2_plugin_proxy *>(gui->plugin);
519
520 if (gui->optwindow)
521 {
522 g_signal_handler_disconnect(gui->optwindow, proxy->window_destroyed_signal);
523 proxy->window_destroyed_signal = 0;
524
525 gtk_widget_hide_all(gui->optwindow);
526 gtk_widget_destroy(gui->optwindow);
527 gui->optwindow = NULL;
528 gui->optclosed = true;
529
530 // idle is no longer called after this, so make sure all events are handled now
531 while (gtk_events_pending())
532 gtk_main_iteration();
533 }
534
535 return 0;
536 }
537
gui_idle(LV2UI_Handle handle)538 int gui_idle(LV2UI_Handle handle)
539 {
540 plugin_gui *gui = (plugin_gui *)handle;
541
542 if (gui->optclosed)
543 return 1;
544
545 if (gui->optwindow)
546 {
547 while (gtk_events_pending())
548 gtk_main_iteration();
549 }
550
551 return 0;
552 }
553
gui_extension(const char * uri)554 const void *gui_extension(const char *uri)
555 {
556 static const LV2UI_Show_Interface show = { gui_show, gui_hide };
557 static const LV2UI_Idle_Interface idle = { gui_idle };
558 if (!strcmp(uri, LV2_UI__showInterface))
559 return &show;
560 if (!strcmp(uri, LV2_UI__idleInterface))
561 return &idle;
562 return NULL;
563 }
564
lv2ui_descriptor(uint32_t index)565 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
566 {
567 static LV2UI_Descriptor gtkgui;
568 gtkgui.URI = "http://calf.sourceforge.net/plugins/gui/gtk2-gui";
569 gtkgui.instantiate = gui_instantiate;
570 gtkgui.cleanup = gui_cleanup;
571 gtkgui.port_event = gui_port_event;
572 gtkgui.extension_data = gui_extension;
573 if (!index--)
574 return >kgui;
575
576 static LV2UI_Descriptor gtkguireq;
577 gtkguireq.URI = "http://calf.sourceforge.net/plugins/gui/gtk2-gui-req";
578 gtkguireq.instantiate = gui_instantiate;
579 gtkguireq.cleanup = gui_cleanup;
580 gtkguireq.port_event = gui_port_event;
581 gtkguireq.extension_data = gui_extension;
582 if (!index--)
583 return >kguireq;
584
585 return NULL;
586 }
587