[RFC,9/9] ui/ncurses: Add plugin menu and nc-plugin screen

Message ID 20170215043541.10204-9-sam@mendozajonas.com
State RFC
Headers show

Commit Message

Samuel Mendoza-Jonas Feb. 15, 2017, 4:35 a.m.
Add a second pmenu accessible via the main menu which displays
uninstalled and installed pb-plugins. Uninstalled options can be
selected to trigger pb-plugin to install them, after which they are
updated and marked as installed in the menu.
Installed plugins can be investigated by entering the new plugin screen,
where plugin metadata and executables are displayed. Executables can be
run from this screen via cui_run_external().

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
---
 ui/common/discover-client.c |  30 ++++
 ui/common/discover-client.h |   6 +
 ui/ncurses/Makefile.am      |   6 +-
 ui/ncurses/nc-boot-editor.c |   3 +-
 ui/ncurses/nc-cui.c         | 298 ++++++++++++++++++++++++++++++--
 ui/ncurses/nc-cui.h         |  18 +-
 ui/ncurses/nc-menu.c        |  32 +++-
 ui/ncurses/nc-menu.h        |   3 +-
 ui/ncurses/nc-plugin-help.c |   4 +
 ui/ncurses/nc-plugin.c      | 402 ++++++++++++++++++++++++++++++++++++++++++++
 ui/ncurses/nc-plugin.h      |  34 ++++
 ui/ncurses/nc-scr.h         |   1 +
 12 files changed, 816 insertions(+), 21 deletions(-)
 create mode 100644 ui/ncurses/nc-plugin-help.c
 create mode 100644 ui/ncurses/nc-plugin.c
 create mode 100644 ui/ncurses/nc-plugin.h

Patch

diff --git a/ui/common/discover-client.c b/ui/common/discover-client.c
index dce74f9..dd8f06e 100644
--- a/ui/common/discover-client.c
+++ b/ui/common/discover-client.c
@@ -112,6 +112,21 @@  static void device_remove(struct discover_client *client, const char *id)
 	talloc_free(device);
 }
 
+static void plugin_option_add(struct discover_client *client,
+		struct plugin_option *opt)
+{
+	talloc_steal(client, opt);
+
+	if (client->ops.plugin_option_add)
+		client->ops.plugin_option_add(opt, client->ops.cb_arg);
+}
+
+static void plugins_remove(struct discover_client *client)
+{
+	if (client->ops.plugins_remove)
+		client->ops.plugins_remove(client->ops.cb_arg);
+}
+
 void discover_client_enumerate(struct discover_client *client)
 {
 	struct boot_option *opt;
@@ -155,6 +170,7 @@  static int discover_client_process(void *arg)
 {
 	struct discover_client *client = arg;
 	struct pb_protocol_message *message;
+	struct plugin_option *p_opt;
 	struct system_info *sysinfo;
 	struct boot_option *opt;
 	struct status *status;
@@ -235,6 +251,20 @@  static int discover_client_process(void *arg)
 		}
 		update_config(client, config);
 		break;
+	case PB_PROTOCOL_ACTION_PLUGIN_OPTION_ADD:
+		p_opt = talloc_zero(ctx, struct plugin_option);
+
+		rc = pb_protocol_deserialise_plugin_option(p_opt, message);
+		if (rc) {
+			pb_log("%s: no plugin_option?\n", __func__);
+			goto out;
+		}
+
+		plugin_option_add(client, p_opt);
+		break;
+	case PB_PROTOCOL_ACTION_PLUGINS_REMOVE:
+		plugins_remove(client);
+		break;
 	default:
 		pb_log("%s: unknown action %d\n", __func__, message->action);
 	}
diff --git a/ui/common/discover-client.h b/ui/common/discover-client.h
index 95a5d9e..0a110b1 100644
--- a/ui/common/discover-client.h
+++ b/ui/common/discover-client.h
@@ -14,6 +14,10 @@  struct pb_boot_data {
 	char *args_sig_file;
 };
 
+struct pb_plugin_data {
+	char *plugin_file;
+};
+
 /**
  * struct discover_client_ops - Application supplied client info.
  * @device_add: PB_PROTOCOL_ACTION_ADD event callback.
@@ -35,6 +39,8 @@  struct discover_client_ops {
 	int (*boot_option_add)(struct device *dev, struct boot_option *option,
 			void *arg);
 	void (*device_remove)(struct device *device, void *arg);
+	int (*plugin_option_add)(struct plugin_option *option, void *arg);
+	int (*plugins_remove)(void *arg);
 	void (*update_status)(struct status *status, void *arg);
 	void (*update_sysinfo)(struct system_info *sysinfo, void *arg);
 	void (*update_config)(struct config *sysinfo, void *arg);
diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am
index feec51d..a17e2a9 100644
--- a/ui/ncurses/Makefile.am
+++ b/ui/ncurses/Makefile.am
@@ -51,8 +51,10 @@  ui_ncurses_libpbnc_la_SOURCES = \
 	ui/ncurses/nc-subset.c \
 	ui/ncurses/nc-subset.h \
 	ui/ncurses/nc-statuslog.c \
-	ui/ncurses/nc-statuslog.h
-
+	ui/ncurses/nc-statuslog.h \
+	ui/ncurses/nc-plugin.c \
+	ui/ncurses/nc-plugin.h \
+	ui/ncurses/nc-plugin-help.c
 
 sbin_PROGRAMS += ui/ncurses/petitboot-nc
 
diff --git a/ui/ncurses/nc-boot-editor.c b/ui/ncurses/nc-boot-editor.c
index 45c8f94..020a555 100644
--- a/ui/ncurses/nc-boot-editor.c
+++ b/ui/ncurses/nc-boot-editor.c
@@ -633,7 +633,8 @@  struct boot_editor *boot_editor_init(struct cui *cui,
 	nc_scr_frame_draw(&boot_editor->scr);
 
 	if (item) {
-		struct pb_boot_data *bd = cod_from_item(item)->bd;
+		struct pb_boot_data *bd =
+			((struct cui_boot_opt_data *)cod_from_item(item))->bd;
 		boot_editor->image = bd->image;
 		boot_editor->initrd = bd->initrd;
 		boot_editor->dtb = bd->dtb;
diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
index c80d354..351f6fd 100644
--- a/ui/ncurses/nc-cui.c
+++ b/ui/ncurses/nc-cui.c
@@ -44,12 +44,14 @@ 
 #include "nc-helpscreen.h"
 #include "nc-statuslog.h"
 #include "nc-subset.h"
+#include "nc-plugin.h"
 
 extern const struct help_text main_menu_help_text;
 
 static bool cui_detached = false;
 
 static struct pmenu *main_menu_init(struct cui *cui);
+static struct pmenu *plugin_menu_init(struct cui *cui);
 
 static bool lockdown_active(void)
 {
@@ -200,7 +202,7 @@  static int cui_boot(struct pmenu_item *item)
 {
 	int result;
 	struct cui *cui = cui_from_item(item);
-	struct cui_opt_data *cod = cod_from_item(item);
+	struct cui_boot_opt_data *cod = cod_from_item(item);
 
 	assert(cui->current == &cui->main->scr);
 
@@ -223,7 +225,7 @@  static void cui_boot_editor_on_exit(struct cui *cui,
 		struct pb_boot_data *bd)
 {
 	struct pmenu *menu = cui->main;
-	struct cui_opt_data *cod;
+	struct cui_boot_opt_data *cod;
 	int idx, top, rows, cols;
 	static int user_idx = 0;
 
@@ -239,7 +241,7 @@  static void cui_boot_editor_on_exit(struct cui *cui,
 	if (!item) {
 		int insert_pt;
 
-		cod = talloc_zero(NULL, struct cui_opt_data);
+		cod = talloc_zero(NULL, struct cui_boot_opt_data);
 		cod->name = talloc_asprintf(cod, _("User item %u"), ++user_idx);
 
 		item = pmenu_item_create(menu, cod->name);
@@ -369,12 +371,37 @@  static void cui_add_url_exit(struct cui *cui)
 	cui->add_url_screen = NULL;
 }
 
+static void cui_plugin_exit(struct cui *cui)
+{
+	cui_set_current(cui, &cui->plugin_menu->scr);
+	talloc_free(cui->plugin_screen);
+	cui->plugin_screen = NULL;
+}
+
+static void cui_plugin_menu_exit(struct pmenu *menu)
+{
+	struct cui *cui = cui_from_pmenu(menu);
+	cui_set_current(cui, &cui->main->scr);
+}
+
 void cui_show_add_url(struct cui *cui)
 {
 	cui->add_url_screen = add_url_screen_init(cui, cui_add_url_exit);
 	cui_set_current(cui, add_url_screen_scr(cui->add_url_screen));
 }
 
+void cui_show_plugin_menu(struct cui *cui)
+{
+	cui_set_current(cui, &cui->plugin_menu->scr);
+}
+
+void cui_show_plugin(struct pmenu_item *item)
+{
+	struct cui *cui = cui_from_item(item);
+	cui->plugin_screen = plugin_screen_init(cui, item, cui_plugin_exit);
+	cui_set_current(cui, plugin_screen_scr(cui->plugin_screen));
+}
+
 static void cui_help_exit(struct cui *cui)
 {
 	cui_set_current(cui, help_screen_return_scr(cui->help_screen));
@@ -607,6 +634,71 @@  int cui_run_external(struct cui *cui, struct process *process)
 }
 
 /**
+ * cui_plugin_option_add_uninstalled - Add a new, uninstalled plugin option
+ * to the plugin menu.
+ */
+static int cui_plugin_option_add_uninstalled(struct cui *cui, const char *name,
+		const char *plugin_file)
+{
+	struct cui_plugin_opt_data *cod;
+	int result;
+	struct pmenu_item *item;
+	int insert_pt;
+	unsigned int i;
+
+	item = pmenu_item_create(cui->plugin_menu, name);
+	item->on_execute = plugin_install_plugin;
+	item->on_edit = NULL;
+	item->data = cod = talloc(item, struct cui_plugin_opt_data);
+
+	cod->opt = NULL;	/* No opt until it's installed */
+	cod->name = talloc_strdup(cod, name);
+	cod->pd = talloc(item, struct pb_plugin_data);
+	cod->pd->plugin_file = talloc_strdup(cod->pd, plugin_file);
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_unpost(cui->current);
+
+	/* This disconnects items array from menu. */
+	result = set_menu_items(cui->plugin_menu->ncm, NULL);
+
+	insert_pt = pmenu_grow(cui->plugin_menu, 1);
+	pmenu_item_insert(cui->plugin_menu, item, insert_pt);
+
+	result = set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_post(cui->current);
+
+	/* Update the "Plugins" menu item in the main menu */
+	if (nc_scr_active(cui, &cui->main->scr))
+		nc_scr_unpost(cui->current);
+
+	result = set_menu_items(cui->main->ncm, NULL);
+	for (i = 0 ; i < cui->main->item_count; i++) {
+		item = item_userptr(cui->main->items[i]);
+		if (strncmp(item->nci->name.str, "Plugins", strlen("Plugins")))
+			continue;
+		(*((unsigned int *)item->data))++;
+		char *label = talloc_asprintf(item, "Plugins (%u)",
+				*((unsigned int *)item->data));
+		pmenu_item_update(item, label);
+		talloc_free(label);
+		break;
+	}
+
+	result = set_menu_items(cui->main->ncm, cui->main->items);
+
+	if (nc_scr_active(cui, &cui->main->scr))
+		nc_scr_post(cui->current);
+
+	if (result)
+		pb_log("%s: set_menu_items failed: %d\n", __func__, result);
+
+	return result;
+}
+
+/**
  * cui_device_add - Client device_add callback.
  *
  * Creates menu_items for all the device boot_options and inserts those
@@ -618,7 +710,7 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
 {
 	struct pmenu_item *i, *dev_hdr = NULL;
 	struct cui *cui = cui_from_arg(arg);
-	struct cui_opt_data *cod;
+	struct cui_boot_opt_data *cod;
 	const char *tab = "  ";
 	unsigned int insert_pt;
 	int result, rows, cols;
@@ -627,6 +719,14 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
 
 	pb_debug("%s: %p %s\n", __func__, opt, opt->id);
 
+	/* Plugins are discovered via the same mechanism but are handled
+	 * differently */
+	if (opt->type == DISCOVER_PLUGIN_OPTION) {
+		return cui_plugin_option_add_uninstalled(cui, opt->name,
+				opt->boot_image_file);
+		talloc_free(opt);
+	}
+
 	selected = current_item(cui->main->ncm);
 	menu_format(cui->main->ncm, &rows, &cols);
 
@@ -648,7 +748,7 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
 
 	i->on_edit = cui_item_edit;
 	i->on_execute = cui_boot;
-	i->data = cod = talloc(i, struct cui_opt_data);
+	i->data = cod = talloc(i, struct cui_boot_opt_data);
 
 	cod->opt = opt;
 	cod->dev = dev;
@@ -725,7 +825,6 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
  * Removes all the menu_items for the device from the main menu and redraws the
  * main menu if it is active.
  */
-
 static void cui_device_remove(struct device *dev, void *arg)
 {
 	struct cui *cui = cui_from_arg(arg);
@@ -759,7 +858,7 @@  static void cui_device_remove(struct device *dev, void *arg)
 		if (!item || !item->data )
 			continue;
 
-		struct cui_opt_data *data = item->data;
+		struct cui_boot_opt_data *data = item->data;
 		if (data && data->dev && data->dev == dev)
 			pmenu_remove(cui->main,item);
 	}
@@ -801,6 +900,123 @@  static void cui_update_status(struct status *status, void *arg)
 		nc_scr_status_printf(cui->current, "%s", status->message);
 }
 
+/**
+ * Handle a new (installed) plugin option and update its associated
+ * (uninstalled) menu item if it exists.
+ */
+static int cui_plugin_option_add(struct plugin_option *opt, void *arg)
+{
+	struct cui_plugin_opt_data *cod = NULL;
+	struct cui *cui = cui_from_arg(arg);
+	struct pmenu_item *item = NULL;
+	unsigned int i;
+	int result;
+
+fallback:
+	/* Find uninstalled plugin by matching on plugin_file */
+	for (i = 0; i < cui->plugin_menu->item_count; i++) {
+		item = item_userptr(cui->plugin_menu->items[i]);
+		if (!item)
+			continue;
+		cod = cod_from_item(item);
+		if (!cod)
+			continue;
+		if (strncmp(cod->pd->plugin_file, opt->plugin_file,
+					strlen(cod->pd->plugin_file)) == 0)
+			break;
+	}
+
+	if (!item || i >= cui->plugin_menu->item_count) {
+		cui_plugin_option_add_uninstalled(cui, opt->id,
+				opt->plugin_file);
+		pb_debug("New plugin option %s doesn't have a source item\n",
+				opt->id);
+		goto fallback;
+	}
+
+	talloc_free(cod->name);
+	cod->name = talloc_asprintf(cod, "%s [installed]", opt->name);
+
+	cod->opt = opt;
+	item->on_execute = NULL;
+	item->on_edit = cui_show_plugin;
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_unpost(cui->current);
+
+	/* This disconnects items array from menu. */
+	result = set_menu_items(cui->plugin_menu->ncm, NULL);
+
+	if (result == E_OK)
+		pmenu_item_update(item, cod->name);
+
+	/* Re-attach the items array. */
+	result = set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_post(cui->current);
+
+	return result;
+}
+
+static int cui_plugins_remove(void *arg)
+{
+	struct cui *cui = cui_from_arg(arg);
+	struct pmenu_item *item = NULL;
+	unsigned int i;
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_unpost(cui->current);
+	if (nc_scr_active(cui, &cui->main->scr))
+		nc_scr_unpost(cui->current);
+
+	/* This disconnects items array from menu. */
+	set_menu_items(cui->plugin_menu->ncm, NULL);
+
+	for (i = 0; i < cui->plugin_menu->item_count; i++) {
+		item = item_userptr(cui->plugin_menu->items[i]);
+		if (!item || !item->data)
+			continue;
+
+		pmenu_remove(cui->plugin_menu, item);
+		/* plugin_menu entries will shift, jump to bottom to make sure
+		 * we remove all plugin option items */
+		i = 0;
+	}
+
+	/* Re-attach the items array. */
+	set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
+
+	set_menu_items(cui->main->ncm, NULL);
+	for (i = 0; i < cui->main->item_count; i++) {
+		item = item_userptr(cui->main->items[i]);
+		if (strncmp(item->nci->name.str, "Plugins", strlen("Plugins")))
+			continue;
+		*((unsigned int *)item->data) = 0;
+		pmenu_item_update(item, "Plugins (0)");
+		break;
+	}
+
+	set_menu_items(cui->main->ncm, cui->main->items);
+
+	if (nc_scr_active(cui, &cui->plugin_menu->scr))
+		nc_scr_post(cui->current);
+	if (nc_scr_active(cui, &cui->main->scr))
+		nc_scr_post(cui->current);
+
+	/* 
+	 * If our current screen is the plugin screen and we're suspended (ie.
+	 * running a program, move back to the plugin screen since the plugin
+	 * will have been removed. Post/Unpost will be handled in the callback.
+	 */
+	if (cui->suspended && cui->plugin_screen)
+		cui->current = &cui->main->scr;
+
+
+
+	return 0;
+}
+
 static void cui_update_mm_title(struct cui *cui)
 {
 	struct nc_frame *frame = &cui->main->scr.frame;
@@ -852,12 +1068,14 @@  static void cui_update_language(struct cui *cui, char *lang)
 	setlocale(LC_ALL, lang);
 
 	/* we'll need to update the menu: drop all items and repopulate */
-	repost_menu = cui->current == &cui->main->scr;
+	repost_menu = cui->current == &cui->main->scr ||
+		cui->current == &cui->plugin_menu->scr;
 	if (repost_menu)
 		nc_scr_unpost(cui->current);
 
 	talloc_free(cui->main);
 	cui->main = main_menu_init(cui);
+	cui->plugin_menu = plugin_menu_init(cui);
 
 	if (repost_menu) {
 		cui->current = &cui->main->scr;
@@ -940,6 +1158,13 @@  static int menu_add_url_execute(struct pmenu_item *item)
 	return 0;
 }
 
+static int menu_plugin_execute(struct pmenu_item *item)
+{
+	if (cui_from_item(item)->client)
+		cui_show_plugin_menu(cui_from_item(item));
+	return 0;
+}
+
 /**
  * pb_mm_init - Setup the main menu instance.
  */
@@ -950,7 +1175,7 @@  static struct pmenu *main_menu_init(struct cui *cui)
 	int result;
 	bool lockdown = lockdown_active();
 
-	m = pmenu_init(cui, 8, cui_on_exit);
+	m = pmenu_init(cui, 9, cui_on_exit);
 	if (!m) {
 		pb_log("%s: failed\n", __func__);
 		return NULL;
@@ -996,12 +1221,18 @@  static struct pmenu *main_menu_init(struct cui *cui)
 	i->on_execute = menu_add_url_execute;
 	pmenu_item_insert(m, i, 6);
 
+	i = pmenu_item_create(m, _("Plugins (0)"));
+	/* Use this item's data pointer to hold the number of known plugins */
+	i->data = talloc_zero(i, unsigned int);
+	i->on_execute = menu_plugin_execute;
+	pmenu_item_insert(m, i, 7);
+
 	if (lockdown)
 		i = pmenu_item_create(m, _("Reboot"));
 	else
 		i = pmenu_item_create(m, _("Exit to shell"));
 	i->on_execute = pmenu_exit_cb;
-	pmenu_item_insert(m, i, 7);
+	pmenu_item_insert(m, i, 8);
 
 	result = pmenu_setup(m);
 
@@ -1025,10 +1256,53 @@  fail_setup:
 	return NULL;
 }
 
+/*
+ * plugin_menu_init: Set up the plugin menu instance
+ */
+static struct pmenu *plugin_menu_init(struct cui *cui)
+{
+	struct pmenu_item *i;
+	struct pmenu *m;
+	int result;
+
+	m = pmenu_init(cui, 2, cui_plugin_menu_exit);
+	m->on_new = cui_item_new;
+	m->scr.frame.ltitle = talloc_asprintf(m, _("Petitboot Plugins"));
+	m->scr.frame.rtitle = talloc_asprintf(m, NULL);
+	m->scr.frame.help = talloc_strdup(m,
+		_("Enter=accept, e=edit, x=exit, h=help"));
+	m->scr.frame.status = talloc_asprintf(m, NULL);
+
+	/* add a separator */
+	i = pmenu_item_create(m, " ");
+	item_opts_off(i->nci, O_SELECTABLE);
+	pmenu_item_insert(m, i, 0);
+
+	i = pmenu_item_create(m, ("Return to Main Menu"));
+	i->on_execute = pmenu_exit_cb;
+	pmenu_item_insert(m, i, 1);
+
+	result = pmenu_setup(m);
+
+	if (result) {
+		pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
+			strerror(errno));
+		goto fail_setup;
+	}
+
+	return m;
+
+fail_setup:
+	talloc_free(m);
+	return NULL;
+}
+
 static struct discover_client_ops cui_client_ops = {
 	.device_add = NULL,
 	.boot_option_add = cui_boot_option_add,
 	.device_remove = cui_device_remove,
+	.plugin_option_add = cui_plugin_option_add,
+	.plugins_remove = cui_plugins_remove,
 	.update_status = cui_update_status,
 	.update_sysinfo = cui_update_sysinfo,
 	.update_config = cui_update_config,
@@ -1183,6 +1457,10 @@  retry_start:
 	if (!cui->main)
 		goto fail_client_init;
 
+	cui->plugin_menu = plugin_menu_init(cui);
+	if (!cui->plugin_menu)
+		goto fail_client_init;
+
 	waiter_register_io(cui->waitset, STDIN_FILENO, WAIT_IN,
 			cui_process_key, cui);
 
diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h
index 2171304..ec7abfe 100644
--- a/ui/ncurses/nc-cui.h
+++ b/ui/ncurses/nc-cui.h
@@ -27,8 +27,8 @@ 
 
 struct process;
 
-struct cui_opt_data {
-	const char *name;
+struct cui_boot_opt_data {
+	char *name;
 	struct pb_boot_data *bd;
 
 	/* optional data */
@@ -37,6 +37,14 @@  struct cui_opt_data {
 	uint32_t opt_hash;
 };
 
+struct cui_plugin_opt_data {
+	char *name;
+	struct pb_plugin_data *pd;
+
+	/* optional data */
+	const struct plugin_option *opt;
+};
+
 /**
  * struct cui - Data structure defining a cui state machine.
  * @c_sig: Signature for callback type checking, should be cui_sig.
@@ -56,6 +64,7 @@  struct cui {
 	struct nc_scr *current;
 	struct nc_scr *suspended;
 	struct pmenu *main;
+	struct pmenu *plugin_menu;
 	struct waitset *waitset;
 	struct discover_client *client;
 	struct system_info *sysinfo;
@@ -64,6 +73,7 @@  struct cui {
 	struct config *config;
 	struct config_screen *config_screen;
 	struct add_url_screen *add_url_screen;
+	struct plugin_screen *plugin_screen;
 	struct boot_editor *boot_editor;
 	struct lang_screen *lang_screen;
 	struct help_screen *help_screen;
@@ -72,7 +82,7 @@  struct cui {
 	struct pjs *pjs;
 	void *platform_info;
 	unsigned int default_item;
-	int (*on_boot)(struct cui *cui, struct cui_opt_data *cod);
+	int (*on_boot)(struct cui *cui, struct cui_boot_opt_data *cod);
 };
 
 struct cui *cui_init(void* platform_info,
@@ -91,6 +101,8 @@  void cui_show_help(struct cui *cui, const char *title,
 void cui_show_subset(struct cui *cui, const char *title,
 		void *arg);
 void cui_show_add_url(struct cui *cui);
+void cui_show_plugin(struct pmenu_item *item);
+void cui_show_plugin_menu(struct cui *cui);
 int cui_send_config(struct cui *cui, struct config *config);
 int cui_send_url(struct cui *cui, char *url);
 void cui_send_reinit(struct cui *cui);
diff --git a/ui/ncurses/nc-menu.c b/ui/ncurses/nc-menu.c
index 90a2c0a..39ee91b 100644
--- a/ui/ncurses/nc-menu.c
+++ b/ui/ncurses/nc-menu.c
@@ -124,6 +124,30 @@  static const char *pmenu_item_label(struct pmenu_item *item, const char *name)
 }
 
 /**
+ * pmenu_item_update - Update the label of an existing pmenu_item.
+ *
+ * The item array must be disconnected prior to calling.
+ */
+int pmenu_item_update(struct pmenu_item *item, const char *name)
+{
+	ITEM *i = item->nci;
+	const char *label;
+
+	if (!item || !item->nci)
+		return -1;
+
+	label = pmenu_item_label(item, name);
+
+	if (!label)
+		return -1;
+
+	i->name.str = label;
+	i->name.length = strncols(label);
+
+	return 0;
+}
+
+/**
  * pmenu_item_create - Allocate and initialize a new pmenu_item instance.
  *
  * Returns a pointer the the initialized struct pmenu_item instance or NULL
@@ -174,7 +198,7 @@  void pmenu_item_insert(struct pmenu *menu, struct pmenu_item *item,
 void pmenu_item_add(struct pmenu *menu, struct pmenu_item *item,
 	unsigned int insert_pt)
 {
-	struct cui_opt_data *cod = item->data;
+	struct cui_boot_opt_data *cod = item->data;
 	bool found = false;
 	unsigned int dev;
 
@@ -185,7 +209,7 @@  void pmenu_item_add(struct pmenu *menu, struct pmenu_item *item,
 			continue;
 
 		struct pmenu_item *i = item_userptr(menu->items[dev]);
-		struct cui_opt_data *d = i->data;
+		struct cui_boot_opt_data *d = i->data;
 		/* Device header will have opt == NULL */
 		if (d && !d->opt) {
 			if (cod->dev == d->dev) {
@@ -222,7 +246,7 @@  struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev,
 	bool newdev = true, matched = false;
 	struct interface_info *intf;
 	struct blockdev_info *bd;
-	struct cui_opt_data *cod;
+	struct cui_boot_opt_data *cod;
 	struct system_info *sys;
 	char hwaddr[32];
 	unsigned int i;
@@ -314,7 +338,7 @@  struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev,
 
 	/* We identify dev_hdr items as having a valid c->name,
 	 * but a NULL c->opt */
-	cod = talloc(dev_hdr, struct cui_opt_data);
+	cod = talloc(dev_hdr, struct cui_boot_opt_data);
 	cod->name = talloc_strdup(dev_hdr, opt->device_id);
 	cod->dev = dev;
 	cod->opt = NULL;
diff --git a/ui/ncurses/nc-menu.h b/ui/ncurses/nc-menu.h
index 136bf66..14e476a 100644
--- a/ui/ncurses/nc-menu.h
+++ b/ui/ncurses/nc-menu.h
@@ -53,6 +53,7 @@  struct pmenu_item {
 	int (*on_execute)(struct pmenu_item *item);
 };
 
+int pmenu_item_update(struct pmenu_item *item, const char *name);
 struct pmenu_item *pmenu_item_create(struct pmenu *menu, const char *name);
 struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev,
 	struct boot_option *opt);
@@ -70,7 +71,7 @@  static inline struct pmenu_item *pmenu_item_from_arg(void *arg)
 	return item;
 }
 
-static inline struct cui_opt_data *cod_from_item(struct pmenu_item *item)
+static inline void *cod_from_item(struct pmenu_item *item)
 {
 	return item->data;
 }
diff --git a/ui/ncurses/nc-plugin-help.c b/ui/ncurses/nc-plugin-help.c
new file mode 100644
index 0000000..79ed964
--- /dev/null
+++ b/ui/ncurses/nc-plugin-help.c
@@ -0,0 +1,4 @@ 
+#include "nc-helpscreen.h"
+
+struct help_text plugin_help_text = define_help_text("\
+Useful help text here");
diff --git a/ui/ncurses/nc-plugin.c b/ui/ncurses/nc-plugin.c
new file mode 100644
index 0000000..15dac4e
--- /dev/null
+++ b/ui/ncurses/nc-plugin.c
@@ -0,0 +1,402 @@ 
+/*
+ *  Copyright (C) 2017 IBM Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  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
+ */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <talloc/talloc.h>
+#include <types/types.h>
+#include <i18n/i18n.h>
+#include <log/log.h>
+
+#include "nc-cui.h"
+#include "nc-plugin.h"
+#include "nc-widgets.h"
+#include "nc-menu.h"
+#include "ui/common/discover-client.h"
+#include "process/process.h"
+#include "system/system.h"
+
+#define N_FIELDS        15
+
+extern const struct help_text plugin_help_text;
+
+struct plugin_screen {
+	struct nc_scr		scr;
+	struct cui		*cui;
+	struct nc_widgetset	*widgetset;
+	WINDOW			*pad;
+
+	bool			exit;
+	bool			show_help;
+	bool			need_redraw;
+	void			(*on_exit)(struct cui *);
+
+	int			label_x;
+	int			field_x;
+	int			scroll_y;
+
+	const struct plugin_option	*opt;
+
+	struct {
+		struct nc_widget_label		*id_l;
+		struct nc_widget_label		*id_f;
+		struct nc_widget_label		*name_l;
+		struct nc_widget_label		*name_f;
+		struct nc_widget_label		*vendor_l;
+		struct nc_widget_label		*vendor_f;
+		struct nc_widget_label		*vendor_id_l;
+		struct nc_widget_label		*vendor_id_f;
+		struct nc_widget_label		*version_l;
+		struct nc_widget_label		*version_f;
+		struct nc_widget_label		*date_l;
+		struct nc_widget_label		*date_f;
+
+		struct nc_widget_label		*commands_l;
+		struct nc_widget_select		*commands_f;
+
+		struct nc_widget_button		*run_b;
+	} widgets;
+};
+
+static struct plugin_screen *plugin_screen_from_scr(struct nc_scr *scr)
+{
+	struct plugin_screen *plugin_screen;
+
+	assert(scr->sig == pb_plugin_screen_sig);
+	plugin_screen = (struct plugin_screen *)
+		((char *)scr - (size_t)&((struct plugin_screen *)0)->scr);
+	assert(plugin_screen->scr.sig == pb_plugin_screen_sig);
+	return plugin_screen;
+}
+
+struct nc_scr *plugin_screen_scr(struct plugin_screen *screen)
+{
+	return &screen->scr;
+}
+
+static void pad_refresh(struct plugin_screen *screen)
+{
+	int y, x, rows, cols;
+
+	getmaxyx(screen->scr.sub_ncw, rows, cols);
+	getbegyx(screen->scr.sub_ncw, y, x);
+
+	prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols);
+}
+
+static void plugin_screen_widget_focus(struct nc_widget *widget, void *arg)
+{
+	struct plugin_screen *screen = arg;
+	int w_y, w_height, w_focus, s_max, adjust;
+
+	w_height = widget_height(widget);
+	w_focus = widget_focus_y(widget);
+	w_y = widget_y(widget) + w_focus;
+	s_max = getmaxy(screen->scr.sub_ncw) - 1;
+
+	if (w_y < screen->scroll_y)
+		screen->scroll_y = w_y;
+
+	else if (w_y + screen->scroll_y + 1 > s_max) {
+		/* Fit as much of the widget into the screen as possible */
+		adjust = min(s_max - 1, w_height - w_focus);
+		if (w_y + adjust >= screen->scroll_y + s_max)
+			screen->scroll_y = max(0, 1 + w_y + adjust - s_max);
+	} else
+		return;
+
+	pad_refresh(screen);
+}
+
+static void plugin_screen_process_key(struct nc_scr *scr, int key)
+{
+	struct plugin_screen *screen = plugin_screen_from_scr(scr);
+	bool handled;
+
+	pb_log("%s\n", __func__);
+
+	handled = widgetset_process_key(screen->widgetset, key);
+
+	if (!handled) {
+		pb_log("Not handled by widgetset\n");
+		switch (key) {
+		case 'x':
+		case 27: /* esc */
+			screen->exit = true;
+			break;
+		case 'h':
+			screen->show_help = true;
+			break;
+		}
+	}
+
+	if (screen->exit) {
+		screen->on_exit(screen->cui);
+
+	} else if (screen->show_help) {
+		screen->show_help = false;
+		screen->need_redraw = true;
+		cui_show_help(screen->cui, _("Petitboot Plugin"),
+				&plugin_help_text);
+
+	} else if (handled) {
+		pad_refresh(screen);
+	}
+}
+
+static int plugin_screen_post(struct nc_scr *scr)
+{
+	struct plugin_screen *screen = plugin_screen_from_scr(scr);
+
+	widgetset_post(screen->widgetset);
+
+	nc_scr_frame_draw(scr);
+	if (screen->need_redraw) {
+		redrawwin(scr->main_ncw);
+		screen->need_redraw = false;
+	}
+	wrefresh(screen->scr.main_ncw);
+	pad_refresh(screen);
+	return 0;
+}
+
+static int plugin_screen_unpost(struct nc_scr *scr)
+{
+	widgetset_unpost(plugin_screen_from_scr(scr)->widgetset);
+	return 0;
+}
+
+static void plugin_screen_resize(struct nc_scr *scr)
+{
+	/* FIXME: forms can't be resized, need to recreate here */
+	plugin_screen_unpost(scr);
+	plugin_screen_post(scr);
+}
+
+static void plugin_run_command(void *arg)
+{
+	struct plugin_screen *screen = arg;
+	struct process *p;
+	const char *cmd;
+	int i;
+
+	i = widget_select_get_value(screen->widgets.commands_f);
+	cmd = screen->opt->executables[i];
+
+	if (!cmd) {
+		pb_log("nc-plugin: plugin option has missing command %d\n", i);
+		return;
+	}
+
+	// FIXME: cmd may be relative to wrapper directory
+	const char *argv[] = {
+		pb_system_apps.pb_exec,
+		cmd,
+		NULL
+	};
+
+	p = process_create(screen);
+	if (!p) {
+		pb_log("%s failed to create process\n", __func__);
+		return;
+	}
+
+	p->path = pb_system_apps.pb_exec;
+	p->argv = argv;
+	p->raw_stdout = true;
+
+	cui_run_external(screen->cui, p);
+}
+
+/* Call pb-plugin to install a plugin specified by plugin_file */
+int plugin_install_plugin(struct pmenu_item *item)
+{
+	struct cui *cui = cui_from_item(item);
+	struct cui_plugin_opt_data *cod = cod_from_item(item);
+	struct process *p;
+	int result;
+
+	assert(cui->current == &cui->plugin_menu->scr);
+
+	nc_scr_status_printf(cui->current, _("Installing plugin %s"),
+			cod->pd->plugin_file);
+
+	p = process_create(cod);
+	if (!p)
+		return -1;
+
+	const char *argv[] = {
+		pb_system_apps.pb_plugin,
+		"install",
+		"auto",
+		cod->pd->plugin_file,
+		NULL
+	};
+
+
+	p->path = pb_system_apps.pb_plugin;
+	p->argv = argv;
+	p->keep_stdout = true;
+
+	// TODO Run this asynchronously
+	// TODO Communicate to server?
+	result = process_run_sync(p);
+	if (result || p->exit_status)
+		nc_scr_status_printf(cui->current,
+				"Plugin %s failed to install!",
+				cod->pd->plugin_file);
+	else
+		nc_scr_status_printf(cui->current, "Plugin %s is installed!",
+				cod->pd->plugin_file);
+
+	return result;
+}
+
+static void plugin_screen_setup_widgets(struct plugin_screen *screen,
+		struct pmenu_item *item)
+{
+	const struct plugin_option *opt;
+	struct cui_plugin_opt_data *cod;
+	struct nc_widgetset *set;
+	unsigned int i;
+
+	build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
+			== N_FIELDS);
+
+	set = screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
+			screen->pad);
+
+	// FIXME Some issues with visible widget focus
+	widgetset_set_widget_focus(screen->widgetset,
+			plugin_screen_widget_focus, screen);
+
+	cod = cod_from_item(item);
+	screen->opt = opt =  cod->opt;
+
+	screen->widgets.id_l = widget_new_label(set, 0, 0, _("ID:"));
+	screen->widgets.id_f = widget_new_label(set, 0, 0, opt->id);
+	screen->widgets.name_l = widget_new_label(set, 0, 0, _("Name:"));
+	screen->widgets.name_f = widget_new_label(set, 0, 0, opt->name);
+	screen->widgets.vendor_l = widget_new_label(set, 0, 0, _("Vendor:"));
+	screen->widgets.vendor_f = widget_new_label(set, 0, 0, opt->vendor);
+	screen->widgets.vendor_id_l = widget_new_label(set, 0, 0,
+						       _("Vendor ID:"));
+	screen->widgets.vendor_id_f = widget_new_label(set, 0, 0,
+							opt->vendor_id);
+	screen->widgets.version_l = widget_new_label(set, 0, 0, _("Version:"));
+	screen->widgets.version_f = widget_new_label(set, 0, 0,
+							opt->version);
+	screen->widgets.date_l = widget_new_label(set, 0, 0, _("Date"));
+	screen->widgets.date_f = widget_new_label(set, 0, 0, opt->date);
+
+	screen->widgets.commands_l = widget_new_label(set, 0, 0,
+							 _("Commands:"));
+	screen->widgets.commands_f = widget_new_select(set, 0, 0,
+			COLS - screen->field_x - 1);
+	for (i = 0; i < opt->n_executables; i++) {
+		widget_select_add_option(screen->widgets.commands_f, i,
+				opt->executables[i], i == 0);
+	}
+
+	screen->widgets.run_b = widget_new_button(set, 0, 0, 30,
+			_("Run selected command"), plugin_run_command, screen);
+}
+
+static void plugin_screen_layout_widgets(struct plugin_screen *screen)
+{
+	unsigned int y;
+
+	/* list of details (static) */
+
+	y = 1;
+
+	widget_move(widget_label_base(screen->widgets.id_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.id_f), y++, screen->field_x);
+	widget_move(widget_label_base(screen->widgets.name_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.name_f), y++, screen->field_x);
+	widget_move(widget_label_base(screen->widgets.vendor_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.vendor_f), y++, screen->field_x);
+	widget_move(widget_label_base(screen->widgets.vendor_id_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.vendor_id_f), y++, screen->field_x);
+	widget_move(widget_label_base(screen->widgets.version_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.version_f), y++, screen->field_x);
+	widget_move(widget_label_base(screen->widgets.date_l), y++, screen->label_x);
+	widget_move(widget_label_base(screen->widgets.date_f), y++, screen->field_x);
+
+	y += 1;
+
+	/* available commands */
+	widget_move(widget_label_base(screen->widgets.commands_l), y,
+		    screen->label_x);
+	widget_move(widget_select_base(screen->widgets.commands_f), y,
+			screen->field_x);
+
+	y += 2;
+
+	widget_move(widget_button_base(screen->widgets.run_b), y++, screen->field_x);
+
+}
+
+static int plugin_screen_destroy(void *arg)
+{
+	struct plugin_screen *screen = arg;
+	if (screen->pad)
+		delwin(screen->pad);
+	return 0;
+}
+
+struct plugin_screen *plugin_screen_init(struct cui *cui,
+		struct pmenu_item *item,
+		void (*on_exit)(struct cui *))
+{
+	struct plugin_screen *screen = talloc_zero(cui, struct plugin_screen);
+	talloc_set_destructor(screen, plugin_screen_destroy);
+
+	nc_scr_init(&screen->scr, pb_plugin_screen_sig, 0,
+			cui, plugin_screen_process_key,
+			plugin_screen_post, plugin_screen_unpost,
+			plugin_screen_resize);
+
+
+	screen->cui = cui;
+	screen->on_exit = on_exit;
+	screen->label_x = 2;
+	screen->field_x = 25;
+	screen->need_redraw = false;
+
+	screen->scr.frame.ltitle = talloc_strdup(screen,
+			_("Petitboot Plugin"));
+	screen->scr.frame.rtitle = NULL;
+	screen->scr.frame.help = talloc_strdup(screen,
+			_("tab=next, shift+tab=previous, x=exit, h=help"));
+	nc_scr_frame_draw(&screen->scr);
+
+	scrollok(screen->scr.sub_ncw, true);
+
+	plugin_screen_setup_widgets(screen, item);
+	plugin_screen_layout_widgets(screen);
+	wrefresh(screen->scr.main_ncw);
+
+	return screen;
+}
+
diff --git a/ui/ncurses/nc-plugin.h b/ui/ncurses/nc-plugin.h
new file mode 100644
index 0000000..6dfd4ae
--- /dev/null
+++ b/ui/ncurses/nc-plugin.h
@@ -0,0 +1,34 @@ 
+/*
+ *  Copyright (C) 2016 IBM Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  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
+ */
+
+#ifndef _NC_PLUGIN_H
+#define _NC_PLUGIN_H
+
+#include "nc-cui.h"
+
+struct plugin_screen;
+
+struct plugin_screen *plugin_screen_init(struct cui *cui,
+		struct pmenu_item *item,
+                void (*on_exit)(struct cui *));
+
+struct nc_scr *plugin_screen_scr(struct plugin_screen *screen);
+void plugin_screen_update(struct plugin_screen *screen);
+
+int plugin_install_plugin(struct pmenu_item *item);
+
+#endif /* defined _NC_PLUGIN_H */
diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h
index be99b48..5671a6b 100644
--- a/ui/ncurses/nc-scr.h
+++ b/ui/ncurses/nc-scr.h
@@ -49,6 +49,7 @@  enum pb_nc_sig {
 	pb_lang_screen_sig	= 777,
 	pb_add_url_screen_sig	= 888,
 	pb_subset_screen_sig	= 101,
+	pb_plugin_screen_sig	= 202,
 	pb_removed_sig		= -999,
 };