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 */
