| @@ -0,0 +1,388 @@ | |||
| commit 109070a71a1e780385b803731fbdd2689752c644 | |||
| Author: Robin Gareus <robin@gareus.org> | |||
| Date: Tue Jan 7 17:48:52 2014 +0100 | |||
| add support for externalUI | |||
| diff --git a/jalv/src/jalv.c b/jalv/src/jalv.c | |||
| index e80f9f9..e0aa1a0 100644 | |||
| --- a/jalv/src/jalv.c | |||
| +++ b/jalv/src/jalv.c | |||
| @@ -649,32 +649,73 @@ void | |||
| jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) | |||
| { | |||
| jalv->ui_host = suil_host_new(jalv_ui_write, jalv_ui_port_index, NULL, NULL); | |||
| + jalv->extuiptr = NULL; | |||
| + | |||
| + if (jalv->externalui) { | |||
| + | |||
| + const LV2_Feature external_lv_feature = { | |||
| + LV2_EXTERNAL_UI_DEPRECATED_URI, parent | |||
| + }; | |||
| + const LV2_Feature external_kx_feature = { | |||
| + LV2_EXTERNAL_UI__Host, parent | |||
| + }; | |||
| + const LV2_Feature instance_feature = { | |||
| + NS_EXT "instance-access", lilv_instance_get_handle(jalv->instance) | |||
| + }; | |||
| + const LV2_Feature* ui_features[] = { | |||
| + &uri_map_feature, &map_feature, &unmap_feature, | |||
| + &instance_feature, | |||
| + &log_feature, | |||
| + &external_lv_feature, | |||
| + &external_kx_feature, | |||
| + &options_feature, | |||
| + NULL | |||
| + }; | |||
| + | |||
| + jalv->ui_instance = suil_instance_new( | |||
| + jalv->ui_host, | |||
| + jalv, | |||
| + native_ui_type, | |||
| + lilv_node_as_uri(lilv_plugin_get_uri(jalv->plugin)), | |||
| + lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)), | |||
| + lilv_node_as_uri(jalv->ui_type), | |||
| + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(jalv->ui))), | |||
| + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(jalv->ui))), | |||
| + ui_features); | |||
| - const LV2_Feature parent_feature = { | |||
| - LV2_UI__parent, parent | |||
| - }; | |||
| - const LV2_Feature instance_feature = { | |||
| - NS_EXT "instance-access", lilv_instance_get_handle(jalv->instance) | |||
| - }; | |||
| - const LV2_Feature* ui_features[] = { | |||
| - &uri_map_feature, &map_feature, &unmap_feature, | |||
| - &instance_feature, | |||
| - &log_feature, | |||
| - &parent_feature, | |||
| - &options_feature, | |||
| - NULL | |||
| - }; | |||
| + if (jalv->ui_instance) { | |||
| + jalv->extuiptr = suil_instance_get_widget((SuilInstance*)jalv->ui_instance); | |||
| + } else { | |||
| + jalv->externalui = false; | |||
| + } | |||
| + } else { | |||
| - jalv->ui_instance = suil_instance_new( | |||
| - jalv->ui_host, | |||
| - jalv, | |||
| - native_ui_type, | |||
| - lilv_node_as_uri(lilv_plugin_get_uri(jalv->plugin)), | |||
| - lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)), | |||
| - lilv_node_as_uri(jalv->ui_type), | |||
| - lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(jalv->ui))), | |||
| - lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(jalv->ui))), | |||
| - ui_features); | |||
| + const LV2_Feature parent_feature = { | |||
| + LV2_UI__parent, parent | |||
| + }; | |||
| + const LV2_Feature instance_feature = { | |||
| + NS_EXT "instance-access", lilv_instance_get_handle(jalv->instance) | |||
| + }; | |||
| + const LV2_Feature* ui_features[] = { | |||
| + &uri_map_feature, &map_feature, &unmap_feature, | |||
| + &instance_feature, | |||
| + &log_feature, | |||
| + &parent_feature, | |||
| + &options_feature, | |||
| + NULL | |||
| + }; | |||
| + | |||
| + jalv->ui_instance = suil_instance_new( | |||
| + jalv->ui_host, | |||
| + jalv, | |||
| + native_ui_type, | |||
| + lilv_node_as_uri(lilv_plugin_get_uri(jalv->plugin)), | |||
| + lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)), | |||
| + lilv_node_as_uri(jalv->ui_type), | |||
| + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(jalv->ui))), | |||
| + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(jalv->ui))), | |||
| + ui_features); | |||
| + } | |||
| /* Set initial control values on UI */ | |||
| if (jalv->ui_instance) { | |||
| @@ -688,6 +729,7 @@ jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) | |||
| } | |||
| } | |||
| + | |||
| bool | |||
| jalv_ui_is_resizable(Jalv* jalv) | |||
| { | |||
| @@ -795,6 +837,9 @@ jalv_emit_ui_events(Jalv* jalv) | |||
| jalv_ui_port_event(jalv, ev.index, ev.size, ev.protocol, buf); | |||
| } | |||
| } | |||
| + if (jalv->externalui && jalv->extuiptr) { | |||
| + LV2_EXTERNAL_UI_RUN(jalv->extuiptr); | |||
| + } | |||
| return true; | |||
| } | |||
| @@ -938,6 +983,8 @@ main(int argc, char** argv) | |||
| jalv.nodes.rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize); | |||
| jalv.nodes.work_interface = lilv_new_uri(world, LV2_WORKER__interface); | |||
| jalv.nodes.work_schedule = lilv_new_uri(world, LV2_WORKER__schedule); | |||
| + jalv.nodes.ui_externallv = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/ui#external"); | |||
| + jalv.nodes.ui_externalkx = lilv_new_uri(world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget"); | |||
| jalv.nodes.end = NULL; | |||
| /* Get plugin URI from loaded state or command line */ | |||
| @@ -1013,11 +1060,31 @@ main(int argc, char** argv) | |||
| } | |||
| } | |||
| } | |||
| + if (!jalv.ui) { | |||
| + LILV_FOREACH(uis, u, jalv.uis) { | |||
| + const LilvUI* ui = lilv_uis_get(jalv.uis, u); | |||
| + const LilvNodes* types = lilv_ui_get_classes(ui); | |||
| + LILV_FOREACH(nodes, t, types) { | |||
| + const char * pt = lilv_node_as_uri(lilv_nodes_get(types, t)); | |||
| + if (!strcmp(pt, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget")) { | |||
| + jalv.externalui = true; | |||
| + jalv.ui = ui; | |||
| + jalv.ui_type = jalv.nodes.ui_externalkx; | |||
| + } else if (!strcmp(pt, "http://lv2plug.in/ns/extensions/ui#external")) { | |||
| + jalv.externalui = true; | |||
| + jalv.ui_type = jalv.nodes.ui_externallv; | |||
| + jalv.ui = ui; | |||
| + } | |||
| + } | |||
| + } | |||
| + } | |||
| /* Create ringbuffers for UI if necessary */ | |||
| if (jalv.ui) { | |||
| fprintf(stderr, "UI: %s\n", | |||
| lilv_node_as_uri(lilv_ui_get_uri(jalv.ui))); | |||
| + fprintf(stderr, "UI Type: %s\n", | |||
| + lilv_node_as_uri(jalv.ui_type)); | |||
| } else { | |||
| fprintf(stderr, "No appropriate UI found\n"); | |||
| } | |||
| diff --git a/jalv/src/jalv_gtk.c b/jalv/src/jalv_gtk.c | |||
| index bb92dbb..5599c0c 100644 | |||
| --- a/jalv/src/jalv_gtk.c | |||
| +++ b/jalv/src/jalv_gtk.c | |||
| @@ -803,18 +803,28 @@ build_menu(Jalv* jalv, GtkWidget* window, GtkWidget* vbox) | |||
| gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 0); | |||
| } | |||
| +void | |||
| +on_external_ui_closed(void* controller) | |||
| +{ | |||
| + Jalv* jalv = (Jalv*) controller; | |||
| + jalv_close_ui(jalv); | |||
| +} | |||
| + | |||
| int | |||
| jalv_open_ui(Jalv* jalv) | |||
| { | |||
| + LV2_External_UI_Host extui; | |||
| GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); | |||
| jalv->window = window; | |||
| jalv->has_ui = TRUE; | |||
| + extui.ui_closed = on_external_ui_closed; | |||
| g_signal_connect(window, "destroy", | |||
| G_CALLBACK(on_window_destroy), jalv); | |||
| LilvNode* name = lilv_plugin_get_name(jalv->plugin); | |||
| gtk_window_set_title(GTK_WINDOW(window), lilv_node_as_string(name)); | |||
| + extui.plugin_human_id = jalv_strdup(lilv_node_as_string(name)); | |||
| lilv_node_free(name); | |||
| GtkWidget* vbox = new_box(false, 0); | |||
| @@ -832,10 +842,16 @@ jalv_open_ui(Jalv* jalv) | |||
| /* Attempt to instantiate custom UI if necessary */ | |||
| if (jalv->ui && !jalv->opts.generic_ui) { | |||
| - jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), alignment); | |||
| + if (jalv->externalui) { | |||
| + jalv_ui_instantiate(jalv, lilv_node_as_uri(jalv->ui_type), &extui); | |||
| + } else { | |||
| + jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), alignment); | |||
| + } | |||
| } | |||
| - if (jalv->ui_instance) { | |||
| + if (jalv->externalui && jalv->extuiptr) { | |||
| + LV2_EXTERNAL_UI_SHOW(jalv->extuiptr); | |||
| + } else if (jalv->ui_instance) { | |||
| GtkWidget* widget = (GtkWidget*)suil_instance_get_widget( | |||
| jalv->ui_instance); | |||
| @@ -843,6 +859,7 @@ jalv_open_ui(Jalv* jalv) | |||
| gtk_window_set_resizable(GTK_WINDOW(window), jalv_ui_is_resizable(jalv)); | |||
| gtk_widget_show_all(vbox); | |||
| gtk_widget_grab_focus(widget); | |||
| + gtk_window_present(GTK_WINDOW(window)); | |||
| } else { | |||
| GtkWidget* controls = build_control_widget(jalv, window); | |||
| GtkWidget* scroll_win = gtk_scrolled_window_new(NULL, NULL); | |||
| @@ -862,13 +879,12 @@ jalv_open_ui(Jalv* jalv) | |||
| GTK_WINDOW(window), | |||
| MAX(MAX(box_size.width, controls_size.width) + 24, 640), | |||
| box_size.height + controls_size.height); | |||
| + gtk_window_present(GTK_WINDOW(window)); | |||
| } | |||
| g_timeout_add(1000 / jalv->ui_update_hz, | |||
| (GSourceFunc)jalv_emit_ui_events, jalv); | |||
| - gtk_window_present(GTK_WINDOW(window)); | |||
| - | |||
| gtk_main(); | |||
| zix_sem_post(jalv->done); | |||
| return 0; | |||
| diff --git a/jalv/src/jalv_internal.h b/jalv/src/jalv_internal.h | |||
| index 4531ee6..f24c05a 100644 | |||
| --- a/jalv/src/jalv_internal.h | |||
| +++ b/jalv/src/jalv_internal.h | |||
| @@ -44,6 +44,8 @@ | |||
| #include "lv2_evbuf.h" | |||
| #include "symap.h" | |||
| +#include "lv2_external_ui.h" | |||
| + | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #endif | |||
| @@ -139,6 +141,8 @@ typedef struct { | |||
| LilvNode* rsz_minimumSize; | |||
| LilvNode* work_interface; | |||
| LilvNode* work_schedule; | |||
| + LilvNode* ui_externallv; | |||
| + LilvNode* ui_externalkx; | |||
| LilvNode* end; ///< NULL terminator for easy freeing of entire structure | |||
| } JalvNodes; | |||
| @@ -204,6 +208,8 @@ typedef struct { | |||
| bool buf_size_set; ///< True iff buffer size callback fired | |||
| bool exit; ///< True iff execution is finished | |||
| bool has_ui; ///< True iff a control UI is present | |||
| + bool externalui; ///< True iff plugin has an external-ui | |||
| + LV2_External_UI_Widget* extuiptr; ///< data structure used for external-ui | |||
| } Jalv; | |||
| int | |||
| diff --git a/jalv/src/lv2_external_ui.h b/jalv/src/lv2_external_ui.h | |||
| new file mode 100644 | |||
| index 0000000..2c9e6ee | |||
| --- /dev/null | |||
| +++ b/jalv/src/lv2_external_ui.h | |||
| @@ -0,0 +1,109 @@ | |||
| +/* | |||
| + LV2 External UI extension | |||
| + This work is in public domain. | |||
| + | |||
| + This file 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. | |||
| + | |||
| + If you have questions, contact Filipe Coelho (aka falkTX) <falktx@falktx.com> | |||
| + or ask in #lad channel, FreeNode IRC network. | |||
| +*/ | |||
| + | |||
| +/** | |||
| + @file lv2_external_ui.h | |||
| + C header for the LV2 External UI extension <http://kxstudio.sf.net/ns/lv2ext/external-ui>. | |||
| +*/ | |||
| + | |||
| +#ifndef LV2_EXTERNAL_UI_H | |||
| +#define LV2_EXTERNAL_UI_H | |||
| + | |||
| +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" | |||
| + | |||
| +#define LV2_EXTERNAL_UI_URI "http://kxstudio.sf.net/ns/lv2ext/external-ui" | |||
| +#define LV2_EXTERNAL_UI_PREFIX LV2_EXTERNAL_UI_URI "#" | |||
| + | |||
| +#define LV2_EXTERNAL_UI__Host LV2_EXTERNAL_UI_PREFIX "Host" | |||
| +#define LV2_EXTERNAL_UI__Widget LV2_EXTERNAL_UI_PREFIX "Widget" | |||
| + | |||
| +/** This extension used to be defined by a lv2plug.in URI */ | |||
| +#define LV2_EXTERNAL_UI_DEPRECATED_URI "http://lv2plug.in/ns/extensions/ui#external" | |||
| + | |||
| +#ifdef __cplusplus | |||
| +extern "C" { | |||
| +#endif | |||
| + | |||
| +/** | |||
| + * When LV2_EXTERNAL_UI__Widget UI is instantiated, the returned | |||
| + * LV2UI_Widget handle must be cast to pointer to LV2_External_UI_Widget. | |||
| + * UI is created in invisible state. | |||
| + */ | |||
| +typedef struct _LV2_External_UI_Widget { | |||
| + /** | |||
| + * Host calls this function regulary. UI library implementing the | |||
| + * callback may do IPC or redraw the UI. | |||
| + * | |||
| + * @param _this_ the UI context | |||
| + */ | |||
| + void (*run)(struct _LV2_External_UI_Widget * _this_); | |||
| + | |||
| + /** | |||
| + * Host calls this function to make the plugin UI visible. | |||
| + * | |||
| + * @param _this_ the UI context | |||
| + */ | |||
| + void (*show)(struct _LV2_External_UI_Widget * _this_); | |||
| + | |||
| + /** | |||
| + * Host calls this function to make the plugin UI invisible again. | |||
| + * | |||
| + * @param _this_ the UI context | |||
| + */ | |||
| + void (*hide)(struct _LV2_External_UI_Widget * _this_); | |||
| + | |||
| +} LV2_External_UI_Widget; | |||
| + | |||
| +#define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run(ptr) | |||
| +#define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show(ptr) | |||
| +#define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide(ptr) | |||
| + | |||
| +/** | |||
| + * On UI instantiation, host must supply LV2_EXTERNAL_UI__Host feature. | |||
| + * LV2_Feature::data must be pointer to LV2_External_UI_Host. | |||
| + */ | |||
| +typedef struct _LV2_External_UI_Host { | |||
| + /** | |||
| + * Callback that plugin UI will call when UI (GUI window) is closed by user. | |||
| + * This callback will be called during execution of LV2_External_UI_Widget::run() | |||
| + * (i.e. not from background thread). | |||
| + * | |||
| + * After this callback is called, UI is defunct. Host must call LV2UI_Descriptor::cleanup(). | |||
| + * If host wants to make the UI visible again, the UI must be reinstantiated. | |||
| + * | |||
| + * @note When using the depreated URI LV2_EXTERNAL_UI_DEPRECATED_URI, | |||
| + * some hosts will not call LV2UI_Descriptor::cleanup() as they should, | |||
| + * and may call show() again without re-initialization. | |||
| + * | |||
| + * @param controller Host context associated with plugin UI, as | |||
| + * supplied to LV2UI_Descriptor::instantiate(). | |||
| + */ | |||
| + void (*ui_closed)(LV2UI_Controller controller); | |||
| + | |||
| + /** | |||
| + * Optional (may be NULL) "user friendly" identifier which the UI | |||
| + * may display to allow a user to easily associate this particular | |||
| + * UI instance with the correct plugin instance as it is represented | |||
| + * by the host (e.g. "track 1" or "channel 4"). | |||
| + * | |||
| + * If supplied by host, the string will be referenced only during | |||
| + * LV2UI_Descriptor::instantiate() | |||
| + */ | |||
| + const char * plugin_human_id; | |||
| + | |||
| +} LV2_External_UI_Host; | |||
| + | |||
| +#ifdef __cplusplus | |||
| +} /* extern "C" */ | |||
| +#endif | |||
| + | |||
| +#endif /* LV2_EXTERNAL_UI_H */ | |||