/* * CINELERRA * Copyright (C) 2016-2020 William Morrow * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ // shared between parent/child fork #include "language.h" #include "pluginlv2ui.h" #include #include #include #include void PluginLV2UI::update_value(int idx, uint32_t bfrsz, uint32_t typ, const void *bfr) { if( idx >= config.nb_ports ) return; for( int i=0, sz=config.size(); iidx == idx ) { opt->set_value(*(const float*)bfr); updates |= UPDATE_HOST; break; } } } void PluginLV2UI::write_from_ui(void *the, uint32_t idx, uint32_t bfrsz, uint32_t typ, const void *bfr) { ((PluginLV2UI*)the)->update_value(idx, bfrsz, typ, bfr); } uint32_t PluginLV2UI::port_index(void* obj, const char* sym) { PluginLV2UI *the = (PluginLV2UI*)obj; for( int i=0, sz=the->config.size(); iconfig[i]; if( !strcmp(sym, opt->sym) ) return opt->idx; } return UINT32_MAX; } void PluginLV2UI::update_control(int idx, uint32_t bfrsz, uint32_t typ, const void *bfr) { if( !sinst || idx >= config.nb_ports ) return; suil_instance_port_event(sinst, idx, bfrsz, typ, bfr); } uint32_t PluginLV2UI::uri_to_id(LV2_URID_Map_Handle handle, const char *map, const char *uri) { return ((PluginLV2UriTable *)handle)->map(uri); } LV2_URID PluginLV2UI::uri_table_map(LV2_URID_Map_Handle handle, const char *uri) { return ((PluginLV2UriTable *)handle)->map(uri); } const char *PluginLV2UI::uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid) { return ((PluginLV2UriTable *)handle)->unmap(urid); } void PluginLV2UI::lv2ui_instantiate(void *parent) { if ( !ui_host ) { ui_host = suil_host_new( PluginLV2UI::write_from_ui, PluginLV2UI::port_index, 0, 0); // suil_host_set_touch_func(ui_host, cb_touch); } features.remove(); // remove terminating zero ui_features = features.size(); LV2_Handle lilv_handle = lilv_instance_get_handle(inst); features.append(new Lv2Feature(NS_EXT "instance-access", lilv_handle)); const LV2_Descriptor *lilv_desc = lilv_instance_get_descriptor(inst); ext_data = new LV2_Extension_Data_Feature(); ext_data->data_access = lilv_desc->extension_data; features.append(new Lv2Feature(LV2_DATA_ACCESS_URI, ext_data)); features.append(new Lv2Feature(LV2_UI__parent, parent)); features.append(new Lv2Feature(LV2_UI__idleInterface, 0)); features.append(new Lv2Feature(LV2_EXTERNAL_UI_URI, &extui_host)); features.append(new Lv2Feature(LV2_EXTERNAL_UI_URI__KX__Host, &extui_host)); features.append(0); // add new terminating zero const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(lilv_ui)); char* bundle_path = lilv_file_uri_parse(bundle_uri, NULL); const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(lilv_ui)); char* binary_path = lilv_file_uri_parse(binary_uri, NULL); sinst = suil_instance_new(ui_host, this, ui_type, lilv_node_as_uri(lilv_plugin_get_uri(lilv)), lilv_node_as_uri(lilv_ui_get_uri(lilv_ui)), lilv_node_as_uri(lilv_type), bundle_path, binary_path, features); lilv_free(binary_path); lilv_free(bundle_path); } bool PluginLV2UI::lv2ui_resizable() { if( !lilv_ui ) return false; const LilvNode* s = lilv_ui_get_uri(lilv_ui); LilvNode *p = lilv_new_uri(world, LV2_CORE__optionalFeature); LilvNode *fs = lilv_new_uri(world, LV2_UI__fixedSize); LilvNode *nrs = lilv_new_uri(world, LV2_UI__noUserResize); LilvNodes *fs_matches = lilv_world_find_nodes(world, s, p, fs); LilvNodes *nrs_matches = lilv_world_find_nodes(world, s, p, nrs); lilv_nodes_free(nrs_matches); lilv_nodes_free(fs_matches); lilv_node_free(nrs); lilv_node_free(fs); lilv_node_free(p); return !fs_matches && !nrs_matches; } //force= 1:ctls(all),gui(all) 0:changed(ctls) -1:gui(all) int PluginLV2UI::update_lv2_input(float *vals, int force) { int ret = 0; float *ctls = config.ctls; for( int i=0; iidx; float val = vals[idx]; if( !force && ctls[idx] == val ) continue; if( force >= 0 ) { ctls[idx] = val; ++ret; } if( force ) update_control(idx, sizeof(val), 0, &val); } return ret; } void PluginLV2UI::update_lv2_output() { int *ports = config.ports; float *ctls = config.ctls; for( int i=0; istart_gui(); update_lv2_input(config.ctls, 1); connect_ports(config, PORTS_CONTROL | PORTS_ATOM); int n = 0; updates = 0; run_lilv(n); if( gui ) { send_host(LV2_SHOW, 0, 0); hidden = 0; } } void PluginLV2UI::update_host() { // ignore update if( running < 0 ) { running = 1; return; } send_host(LV2_UPDATE, config.ctls, sizeof(float)*config.nb_ports); } static void lv2ui_extui_host_ui_closed(void *p) { PluginLV2UI *ui = (PluginLV2UI *)p; ui->hidden = -1; } int PluginLV2ChildUI::init_ui(const char *path, int sample_rate, int bfrsz) { char *title = PluginLV2UI::title; if( load_lv2(path, title) ) return 1; if( init_lv2(config, sample_rate, bfrsz) ) return 1; lilv_uis = lilv_plugin_get_uis(lilv); if( !gui && lilv_uis && wgt_type ) { LilvNode *gui_type = lilv_new_uri(world, wgt_type); LILV_FOREACH(uis, i, lilv_uis) { const LilvUI *ui = lilv_uis_get(lilv_uis, i); if( lilv_ui_is_supported(ui, suil_ui_supported, gui_type, &lilv_type)) { extui_host.ui_closed = lv2ui_extui_host_ui_closed; gui = new PluginLV2ChildWgtUI(this); lilv_ui = ui; ui_type = wgt_type; break; } } lilv_node_free(gui_type); } if( !gui && lilv_uis && gtk_type ) { LilvNode *gui_type = lilv_new_uri(world, gtk_type); LILV_FOREACH(uis, i, lilv_uis) { const LilvUI *ui = lilv_uis_get(lilv_uis, i); if( lilv_ui_is_supported(ui, suil_ui_supported, gui_type, &lilv_type)) { gui = new PluginLV2ChildGtkUI(this); lilv_ui = ui; ui_type = gtk_type; break; } } lilv_node_free(gui_type); ui_type = gtk_type; } lilv_instance_activate(inst); return 0; } void PluginLV2ChildUI::reset_gui() { if( gui ) gui->reset_gui(); if( sinst ) { suil_instance_free(sinst); sinst = 0; } if( ui_host ) { suil_host_free(ui_host); ui_host = 0; } while( features.size() > ui_features ) features.remove_object(); features.append(0); send_host(LV2_HIDE, 0, 0); hidden = 1; } // main loop int PluginLV2ChildUI::run_ui() { double last_time = 0; int64_t frame_usecs = 1e6 / refreshrate; running = 1; done = 0; while( !done ) { if( hidden < 0 ) { if( gui ) gui->top_level = 0; reset_gui(); if( !is_forked() ) { done = -1; break; } hidden = 1; } if( updates ) { if( (updates & UPDATE_PORTS) ) update_lv2_output(); if( (updates & UPDATE_HOST) ) update_host(); updates = 0; } struct timeval tv; gettimeofday(&tv, 0); double t = tv.tv_sec + tv.tv_usec / 1e6; double dt = t - last_time; last_time = t; int64_t usec = frame_usecs - dt*1e6; if( usec < 0 ) usec = 0; if( child_iteration(usec) < 0 ) done = 1; } running = 0; return 0; } void PluginLV2ChildUI::run_buffer(int shmid) { if( !shm_buffer(shmid) ) return; map_buffer(); connect_ports(config, PORTS_ALL); run_lilv(shm_bfr->samples); shm_bfr->done = 1; } int PluginLV2ChildUI::child_iteration(int64_t usec) { int ret = 0; if( is_forked() ) ret = read_child(usec); else if( gui ) usleep(usec); else ret = -1; if( ret > 0 ) { switch( child_token ) { case LV2_OPEN: { open_bfr_t *open_bfr = (open_bfr_t *)child_data; if( init_ui(open_bfr->path, open_bfr->sample_rate, open_bfr->bfrsz) ) { printf("lv2ui: unable to init: %s\n", open_bfr->path); exit(1); } break; } case LV2_LOAD: { float *vals = (float *)child_data; update_lv2_input(vals, 1); break; } case LV2_CONFIG: { float *vals = (float *)child_data; update_lv2_input(vals, 0); break; } case LV2_UPDATE: { float *vals = (float *)child_data; update_lv2_input(vals, -1); break; } case LV2_SHOW: { start_gui(); break; } case LV2_HIDE: { reset_gui(); break; } case LV2_SHMID: { int shmid = *(int *)child_data; run_buffer(shmid); send_parent(LV2_SHMID, 0, 0); break; } case EXIT_CODE: return -1; } } if( ret >= 0 && gui ) gui->handle_child(); return ret; } int PluginLV2ChildUI::send_host(int64_t token, const void *data, int bytes) { return is_forked() ? send_parent(token, data, bytes) : 0; } PluginLV2GUI::PluginLV2GUI(PluginLV2ChildUI *child_ui) { this->child_ui = child_ui; top_level = 0; } PluginLV2GUI::~PluginLV2GUI() { } PluginLV2ChildGtkUI::PluginLV2ChildGtkUI(PluginLV2ChildUI *child_ui) : PluginLV2GUI(child_ui) { gtk_set_locale(); gtk_init(&child_ui->ac, &child_ui->av); } PluginLV2ChildGtkUI::~PluginLV2ChildGtkUI() { } void PluginLV2ChildGtkUI::reset_gui() { GtkWidget *top_level = (GtkWidget *)this->top_level; if( top_level ) { gtk_widget_destroy(top_level); this->top_level = 0; } } static void lilv_gtk_destroy(GtkWidget* widget, gpointer data) { PluginLV2ChildGtkUI *gui = (PluginLV2ChildGtkUI*)data; gui->child_ui->hidden = -1; } void PluginLV2ChildGtkUI::start_gui() { this->top_level = (void *)gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *top_level = (GtkWidget *)this->top_level; g_signal_connect(top_level, "destroy", G_CALLBACK(lilv_gtk_destroy), this); char *title = child_ui->PluginLV2UI::title; gtk_window_set_title(GTK_WINDOW(top_level), title); GtkWidget *vbox = gtk_vbox_new(FALSE, 0); gtk_window_set_role(GTK_WINDOW(top_level), "plugin_ui"); gtk_container_add(GTK_CONTAINER(top_level), vbox); GtkWidget *alignment = gtk_alignment_new(0.5, 0.5, 1.0, 1.0); gtk_box_pack_start(GTK_BOX(vbox), alignment, TRUE, TRUE, 0); gtk_widget_show(alignment); child_ui->lv2ui_instantiate(alignment); GtkWidget* widget = (GtkWidget*)suil_instance_get_widget(child_ui->sinst); gtk_container_add(GTK_CONTAINER(alignment), widget); gtk_window_set_resizable(GTK_WINDOW(top_level), child_ui->lv2ui_resizable()); gtk_widget_show_all(vbox); gtk_widget_grab_focus(widget); gtk_window_present(GTK_WINDOW(top_level)); } int PluginLV2ChildGtkUI::handle_child() { if( gtk_events_pending() ) { gtk_main_iteration(); } return 1; } PluginLV2ChildWgtUI::PluginLV2ChildWgtUI(PluginLV2ChildUI *child_ui) : PluginLV2GUI(child_ui) { } PluginLV2ChildWgtUI::~PluginLV2ChildWgtUI() { } void PluginLV2ChildWgtUI::reset_gui() { lv2_external_ui *top_level = (lv2_external_ui *)this->top_level; if( top_level ) { LV2_EXTERNAL_UI_HIDE(top_level); this->top_level = 0; } } void PluginLV2ChildWgtUI::start_gui() { child_ui->lv2ui_instantiate(0); this->top_level = (void *)suil_instance_get_widget(child_ui->sinst); lv2_external_ui *top_level = (lv2_external_ui *)this->top_level; if( top_level ) LV2_EXTERNAL_UI_SHOW(top_level); } int PluginLV2ChildWgtUI::handle_child() { lv2_external_ui *top_level = (lv2_external_ui *)this->top_level; if( top_level ) LV2_EXTERNAL_UI_RUN(top_level); return 1; }