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, &params[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 &gtkgui;
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 &gtkguireq;
584 
585     return NULL;
586 }
587