[v2,12/12] ui/ncurses: Add plugin menu and nc-plugin screen

Message ID 20170725064910.31478-13-sam@mendozajonas.com
State Accepted
Headers show

Commit Message

Samuel Mendoza-Jonas July 25, 2017, 6:49 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_cmd().

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
---
 ui/common/discover-client.c      |  48 +++++
 ui/common/discover-client.h      |  10 +
 ui/ncurses/Makefile.am           |   7 +-
 ui/ncurses/nc-cui.c              | 409 ++++++++++++++++++++++++++++++++++----
 ui/ncurses/nc-cui.h              |  13 +-
 ui/ncurses/nc-menu.c             |  29 ++-
 ui/ncurses/nc-menu.h             |   1 +
 ui/ncurses/nc-plugin-help.c      |   7 +
 ui/ncurses/nc-plugin-menu-help.c |   7 +
 ui/ncurses/nc-plugin.c           | 418 +++++++++++++++++++++++++++++++++++++++
 ui/ncurses/nc-plugin.h           |  34 ++++
 ui/ncurses/nc-scr.h              |   1 +
 12 files changed, 938 insertions(+), 46 deletions(-)
 create mode 100644 ui/ncurses/nc-plugin-help.c
 create mode 100644 ui/ncurses/nc-plugin-menu-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..8ad4611 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);
 	}
@@ -404,3 +434,21 @@  int discover_client_send_url(struct discover_client *client,
 
 	return pb_protocol_write_message(client->fd, message);
 }
+
+int discover_client_send_plugin_install(struct discover_client *client,
+		char *file)
+{
+	struct pb_protocol_message *message;
+	int len;
+
+	len = pb_protocol_url_len(file);
+
+	message = pb_protocol_create_message(client,
+				PB_PROTOCOL_ACTION_PLUGIN_INSTALL, len);
+	if (!message)
+		return -1;
+
+	pb_protocol_serialise_url(file, message->payload, len);
+
+	return pb_protocol_write_message(client->fd, message);
+}
diff --git a/ui/common/discover-client.h b/ui/common/discover-client.h
index 95a5d9e..7224691 100644
--- a/ui/common/discover-client.h
+++ b/ui/common/discover-client.h
@@ -14,6 +14,11 @@  struct pb_boot_data {
 	char *args_sig_file;
 };
 
+struct pb_plugin_data {
+	char *plugin_file;
+	struct plugin_option *opt;
+};
+
 /**
  * struct discover_client_ops - Application supplied client info.
  * @device_add: PB_PROTOCOL_ACTION_ADD event callback.
@@ -35,6 +40,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);
@@ -91,5 +98,8 @@  void discover_client_enumerate(struct discover_client *client);
 
 /* Send url to config to the discover server */
 int discover_client_send_url(struct discover_client *client, char *url);
+/* Send plugin file path to discover server to install */
+int discover_client_send_plugin_install(struct discover_client *client,
+		char *file);
 
 #endif
diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am
index feec51d..40e11b8 100644
--- a/ui/ncurses/Makefile.am
+++ b/ui/ncurses/Makefile.am
@@ -51,8 +51,11 @@  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 \
+	ui/ncurses/nc-plugin-menu-help.c
 
 sbin_PROGRAMS += ui/ncurses/petitboot-nc
 
diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
index 84e4bf0..f01a360 100644
--- a/ui/ncurses/nc-cui.c
+++ b/ui/ncurses/nc-cui.c
@@ -44,12 +44,15 @@ 
 #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;
+extern const struct help_text plugin_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)
 {
@@ -376,12 +379,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));
@@ -549,6 +577,7 @@  static void cui_handle_resize(struct cui *cui)
  *
  * Creates menu_items for all the device boot_options and inserts those
  * menu_items into the main menu.  Redraws the main menu if it is active.
+ * If a 'plugin' type boot_option appears the plugin menu is updated instead.
  */
 
 static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
@@ -560,86 +589,128 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
 	const char *tab = "  ";
 	unsigned int insert_pt;
 	int result, rows, cols;
+	struct pmenu *menu;
+	bool plugin_option;
 	ITEM *selected;
 	char *name;
 
+	plugin_option = opt->type == DISCOVER_PLUGIN_OPTION;
+	menu = plugin_option ? cui->plugin_menu : cui->main;
+
 	pb_debug("%s: %p %s\n", __func__, opt, opt->id);
 
-	selected = current_item(cui->main->ncm);
-	menu_format(cui->main->ncm, &rows, &cols);
+	selected = current_item(menu->ncm);
+	menu_format(menu->ncm, &rows, &cols);
 
 	if (cui->current == &cui->main->scr)
 		nc_scr_unpost(cui->current);
+	if (plugin_option && cui->current == &cui->plugin_menu->scr)
+		nc_scr_unpost(cui->current);
 
 	/* Check if the boot device is new */
-	dev_hdr = pmenu_find_device(cui->main, dev, opt);
+	dev_hdr = pmenu_find_device(menu, dev, opt);
 
 	/* All actual boot entries are 'tabbed' across */
-	name = talloc_asprintf(cui->main, "%s%s",
+	name = talloc_asprintf(menu, "%s%s",
 			tab, opt->name ? : "Unknown Name");
 
 	/* Save the item in opt->ui_info for cui_device_remove() */
-	opt->ui_info = i = pmenu_item_create(cui->main, name);
+	opt->ui_info = i = pmenu_item_create(menu, name);
 	talloc_free(name);
 	if (!i)
 		return -1;
 
-	i->on_edit = cui_item_edit;
-	i->on_execute = cui_boot;
-	i->data = cod = talloc(i, struct cui_opt_data);
+	if (plugin_option) {
+		i->on_edit = NULL;
+		i->on_execute = plugin_install_plugin;
+	} else {
+		i->on_edit = cui_item_edit;
+		i->on_execute = cui_boot;
+	}
 
+	i->data = cod = talloc(i, struct cui_opt_data);
 	cod->opt = opt;
 	cod->dev = dev;
 	cod->opt_hash = pb_opt_hash(dev, opt);
 	cod->name = opt->name;
-	cod->bd = talloc(i, struct pb_boot_data);
 
-	cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
-	cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
-	cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
-	cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
-	cod->bd->args_sig_file = talloc_strdup(cod->bd, opt->args_sig_file);
+	if (plugin_option) {
+		cod->pd = talloc(i, struct pb_plugin_data);
+		cod->pd->plugin_file = talloc_strdup(cod,
+				opt->boot_image_file);
+	} else {
+		cod->bd = talloc(i, struct pb_boot_data);
+		cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
+		cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
+		cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
+		cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
+		cod->bd->args_sig_file = talloc_strdup(cod->bd, opt->args_sig_file);
+	}
 
 	/* This disconnects items array from menu. */
-	result = set_menu_items(cui->main->ncm, NULL);
+	result = set_menu_items(menu->ncm, NULL);
 
 	if (result)
 		pb_log("%s: set_menu_items failed: %d\n", __func__, result);
 
 	/* Insert new items at insert_pt. */
 	if (dev_hdr) {
-		insert_pt = pmenu_grow(cui->main, 2);
-		pmenu_item_insert(cui->main, dev_hdr, insert_pt);
+		insert_pt = pmenu_grow(menu, 2);
+		pmenu_item_insert(menu, dev_hdr, insert_pt);
 		pb_log("%s: adding new device hierarchy %s\n",
-			__func__,opt->device_id);
-		pmenu_item_insert(cui->main, i, insert_pt+1);
+			__func__, opt->device_id);
+		pmenu_item_insert(menu, i, insert_pt+1);
 	} else {
-		insert_pt = pmenu_grow(cui->main, 1);
-		pmenu_item_add(cui->main, i, insert_pt);
+		insert_pt = pmenu_grow(menu, 1);
+		pmenu_item_add(menu, i, insert_pt);
 	}
 
-	pb_log("%s: adding opt '%s'\n", __func__, cod->name);
-	pb_log("   image  '%s'\n", cod->bd->image);
-	pb_log("   initrd '%s'\n", cod->bd->initrd);
-	pb_log("   args   '%s'\n", cod->bd->args);
-	pb_log("   argsig '%s'\n", cod->bd->args_sig_file);
+	if (plugin_option) {
+		pb_log("%s: adding plugin '%s'\n", __func__, cod->name);
+		pb_log("   file  '%s'\n", cod->pd->plugin_file);
+	} else {
+		pb_log("%s: adding opt '%s'\n", __func__, cod->name);
+		pb_log("   image  '%s'\n", cod->bd->image);
+		pb_log("   initrd '%s'\n", cod->bd->initrd);
+		pb_log("   args   '%s'\n", cod->bd->args);
+		pb_log("   argsig '%s'\n", cod->bd->args_sig_file);
+	}
+
+	/* Update the plugin menu label if needed */
+	if (plugin_option) {
+		struct pmenu_item *item;
+		unsigned int j;
+		result = set_menu_items(cui->main->ncm, NULL);
+		for (j = 0 ; j < cui->main->item_count; j++) {
+			item = item_userptr(cui->main->items[j]);
+			if (strncmp(item->nci->name.str, "Plugins", strlen("Plugins")))
+				continue;
+			cui->n_plugins++;
+			char *label = talloc_asprintf(item, "Plugins (%u)",
+					cui->n_plugins);
+			pmenu_item_update(item, label);
+			talloc_free(label);
+			break;
+		}
+		result = set_menu_items(cui->main->ncm, cui->main->items);
+	}
 
 	/* Re-attach the items array. */
-	result = set_menu_items(cui->main->ncm, cui->main->items);
+	result = set_menu_items(menu->ncm, menu->items);
 
 	if (result)
 		pb_log("%s: set_menu_items failed: %d\n", __func__, result);
 
 	if (0) {
 		pb_log("%s\n", __func__);
-		pmenu_dump_items(cui->main->items,
-			item_count(cui->main->ncm) + 1);
+		pmenu_dump_items(menu->items,
+			item_count(menu->ncm) + 1);
 	}
 
 	if (!item_visible(selected)) {
 		int idx, top;
 
-		top = top_row(cui->main->ncm);
+		top = top_row(menu->ncm);
 		idx = item_index(selected);
 
 		/* If our index is above the current top row, align
@@ -647,11 +718,13 @@  static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
 		 * bottom */
 		top = top < idx ? idx - rows + 1 : idx;
 
-		set_top_row(cui->main->ncm, top);
-		set_current_item(cui->main->ncm, selected);
+		set_top_row(menu->ncm, top);
+		set_current_item(menu->ncm, selected);
 	}
 
-	if (cui->current == &cui->main->scr)
+	if (cui->current == &menu->scr)
+		nc_scr_post(cui->current);
+	if (plugin_option && cui->current == &cui->main->scr)
 		nc_scr_post(cui->current);
 
 	return 0;
@@ -663,7 +736,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);
@@ -676,10 +748,13 @@  static void cui_device_remove(struct device *dev, void *arg)
 
 	if (cui->current == &cui->main->scr)
 		nc_scr_unpost(cui->current);
+	if (cui->current == &cui->plugin_menu->scr)
+		nc_scr_unpost(cui->current);
 
 	/* This disconnects items array from menu. */
 
 	result = set_menu_items(cui->main->ncm, NULL);
+	result |= set_menu_items(cui->plugin_menu->ncm, NULL);
 
 	if (result)
 		pb_log("%s: set_menu_items failed: %d\n", __func__, result);
@@ -688,7 +763,10 @@  static void cui_device_remove(struct device *dev, void *arg)
 		struct pmenu_item *item = pmenu_item_from_arg(opt->ui_info);
 
 		assert(pb_protocol_device_cmp(dev, cod_from_item(item)->dev));
-		pmenu_remove(cui->main, item);
+		if (opt->type == DISCOVER_PLUGIN_OPTION)
+			pmenu_remove(cui->plugin_menu, item);
+		else
+			pmenu_remove(cui->main, item);
 	}
 
 	/* Manually remove remaining device hierarchy item */
@@ -701,12 +779,23 @@  static void cui_device_remove(struct device *dev, void *arg)
 		if (data && data->dev && data->dev == dev)
 			pmenu_remove(cui->main,item);
 	}
+	/* Look in plugins menu too */
+	for (i=0; i < cui->plugin_menu->item_count; i++) {
+		struct pmenu_item *item = item_userptr(cui->plugin_menu->items[i]);
+		if (!item || !item->data )
+			continue;
+
+		struct cui_opt_data *data = item->data;
+		if (data && data->dev && data->dev == dev)
+			pmenu_remove(cui->plugin_menu,item);
+	}
 
 	/* Re-attach the items array. */
 
 	result = set_menu_items(cui->main->ncm, cui->main->items);
+	result |= set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
 
-	/* Move cursor to 'Exit' menu entry */
+	/* Move cursor to 'Exit' menu entry for the main menu.. */
 	menu_format(cui->main->ncm, &rows, &cols);
 	last = cui->main->item_count - 1;
 	set_current_item(cui->main->ncm, cui->main->items[last]);
@@ -715,6 +804,15 @@  static void cui_device_remove(struct device *dev, void *arg)
 		set_top_row(cui->main->ncm, top);
 	}
 
+	/* ..and the plugin menu */
+	menu_format(cui->plugin_menu->ncm, &rows, &cols);
+	last = cui->plugin_menu->item_count - 1;
+	set_current_item(cui->plugin_menu->ncm, cui->plugin_menu->items[last]);
+	if (!item_visible(cui->plugin_menu->items[last])) {
+		top = last < rows ? 0 : last - rows + 1;
+		set_top_row(cui->plugin_menu->ncm, top);
+	}
+
 	if (result)
 		pb_log("%s: set_menu_items failed: %d\n", __func__, result);
 
@@ -726,6 +824,8 @@  static void cui_device_remove(struct device *dev, void *arg)
 
 	if (cui->current == &cui->main->scr)
 		nc_scr_post(cui->current);
+	if (cui->current == &cui->plugin_menu->scr)
+		nc_scr_post(cui->current);
 }
 
 static void cui_update_status(struct status *status, void *arg)
@@ -739,6 +839,156 @@  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_opt_data *cod;
+	struct cui *cui = cui_from_arg(arg);
+	struct pmenu_item *item = NULL;
+	struct boot_option *dummy_opt;
+	struct device *dev;
+	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 || !cod->pd)
+			continue;
+		if (strncmp(cod->pd->plugin_file, opt->plugin_file,
+					strlen(cod->pd->plugin_file)) == 0)
+			break;
+	}
+
+	/*
+	 * If pb-plugin was run manually there may not be an associated
+	 * plugin-type boot_option. Pass a fake device and option to
+	 * cui_boot_option_add() so we have an item to work with.
+	 */
+	if (!item || i >= cui->plugin_menu->item_count) {
+		pb_log("New plugin option %s doesn't have a source item\n",
+				opt->id);
+		dev = talloc_zero(cui, struct device);
+		dev->id = dev->name = talloc_asprintf(dev, "(unknown)");
+		dev->type = DEVICE_TYPE_UNKNOWN;
+		dummy_opt = talloc_zero(cui, struct boot_option);
+		dummy_opt->device_id = talloc_strdup(dummy_opt, dev->id);
+		dummy_opt->id = dummy_opt->name = talloc_asprintf(dummy_opt, "dummy");
+		dummy_opt->boot_image_file = talloc_strdup(dummy_opt, opt->plugin_file);
+		dummy_opt->type = DISCOVER_PLUGIN_OPTION;
+		cui_boot_option_add(dev, dummy_opt, cui);
+		goto fallback;
+	}
+
+	/*
+	 * If this option was faked above move the context under
+	 * the item so it is cleaned up later in cui_plugins_remove().
+	 */
+	if (strncmp(cod->opt->id, "dummy", strlen("dummy") == 0 &&
+				cod->dev->type == DEVICE_TYPE_UNKNOWN)) {
+		talloc_steal(item, cod->dev);
+		talloc_steal(item, cod->opt);
+	}
+
+	talloc_free(cod->name);
+	/* Name is still tabbed across */
+	cod->name = talloc_asprintf(cod, "  %s [installed]", opt->name);
+
+	cod->pd->opt = opt;
+	item->on_execute = NULL;
+	item->on_edit = cui_show_plugin;
+
+	if (cui->current == &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 (cui->current == &cui->plugin_menu->scr)
+		nc_scr_post(cui->current);
+
+	return result;
+}
+
+/*
+ * Most plugin menu items will be removed via cui_device_remove(). However if
+ * pb-plugin has been run manually it is possible that there a plugin items
+ * not associated with a device - remove them here.
+ */
+static int cui_plugins_remove(void *arg)
+{
+	struct cui *cui = cui_from_arg(arg);
+	struct pmenu_item *item = NULL;
+	struct cui_opt_data *cod;
+	unsigned int i = 0;
+
+	pb_debug("%s\n", __func__);
+
+	if (cui->current == &cui->plugin_menu->scr)
+		nc_scr_unpost(cui->current);
+	if (cui->current == &cui->main->scr)
+		nc_scr_unpost(cui->current);
+
+	/* This disconnects items array from menu. */
+	set_menu_items(cui->plugin_menu->ncm, NULL);
+
+	while (i < cui->plugin_menu->item_count) {
+		item = item_userptr(cui->plugin_menu->items[i]);
+		if (!item || !item->data) {
+			i++;
+			continue;
+		}
+		cod = cod_from_item(item);
+		if (!cod->opt && !cod->dev) {
+			i++;
+			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;
+		cui->n_plugins = 0;
+		pmenu_item_update(item, "Plugins (0)");
+		break;
+	}
+
+	set_menu_items(cui->main->ncm, cui->main->items);
+
+	if (cui->current == &cui->main->scr)
+		nc_scr_post(cui->current);
+
+	/* If we're currently in a plugin screen jump back to the plugin menu */
+	if (cui->plugin_screen &&
+			cui->current == plugin_screen_scr(cui->plugin_screen))
+		cui_plugin_exit(cui);
+
+	return 0;
+}
+
 static void cui_update_mm_title(struct cui *cui)
 {
 	struct nc_frame *frame = &cui->main->scr.frame;
@@ -752,6 +1002,18 @@  static void cui_update_mm_title(struct cui *cui)
 
 	if (cui->current == &cui->main->scr)
 		nc_scr_post(cui->current);
+
+	frame = &cui->plugin_menu->scr.frame;
+
+	talloc_free(frame->rtitle);
+
+	frame->rtitle = talloc_strdup(cui->main, cui->sysinfo->type);
+	if (cui->sysinfo->identifier)
+		frame->rtitle = talloc_asprintf_append(frame->rtitle,
+				" %s", cui->sysinfo->identifier);
+
+	if (cui->current == &cui->main->scr)
+		nc_scr_post(cui->current);
 }
 
 static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
@@ -790,12 +1052,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;
@@ -835,6 +1099,11 @@  int cui_send_url(struct cui *cui, char * url)
 	return discover_client_send_url(cui->client, url);
 }
 
+int cui_send_plugin_install(struct cui *cui, char *file)
+{
+	return discover_client_send_plugin_install(cui->client, file);
+}
+
 void cui_send_reinit(struct cui *cui)
 {
 	discover_client_send_reinit(cui->client);
@@ -878,6 +1147,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.
  */
@@ -888,7 +1164,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;
@@ -934,12 +1210,16 @@  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)"));
+	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);
 
@@ -963,10 +1243,57 @@  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=install, e=details, x=exit, h=help"));
+	m->scr.frame.status = talloc_asprintf(m,
+			_("Available Petitboot Plugins"));
+
+	/* 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;
+	}
+
+	m->help_title = _("plugin menu");
+	m->help_text = &plugin_menu_help_text;
+
+	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,
@@ -1121,6 +1448,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 d8a5f8b..b310f4a 100644
--- a/ui/ncurses/nc-cui.h
+++ b/ui/ncurses/nc-cui.h
@@ -26,8 +26,11 @@ 
 #include "nc-helpscreen.h"
 
 struct cui_opt_data {
-	const char *name;
-	struct pb_boot_data *bd;
+	char *name;
+	union {
+		struct pb_boot_data *bd;
+		struct pb_plugin_data *pd;
+	};
 
 	/* optional data */
 	const struct device *dev;
@@ -53,6 +56,8 @@  struct cui {
 	sig_atomic_t resize;
 	struct nc_scr *current;
 	struct pmenu *main;
+	struct pmenu *plugin_menu;
+	unsigned int n_plugins;
 	struct waitset *waitset;
 	struct discover_client *client;
 	struct system_info *sysinfo;
@@ -61,6 +66,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;
@@ -88,8 +94,11 @@  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);
+int cui_send_plugin_install(struct cui *cui, char *file);
 void cui_send_reinit(struct cui *cui);
 
 /* convenience routines */
diff --git a/ui/ncurses/nc-menu.c b/ui/ncurses/nc-menu.c
index 90a2c0a..1ad2a7d 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
@@ -230,7 +254,7 @@  struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev,
 
 	for (i = 0; i < menu->item_count; i++) {
 		item = item_userptr(menu->items[i]);
-		cod = item->data;
+		cod = cod_from_item(item);
 		/* boot entries will have opt defined */
 		if (!cod || cod->opt)
 			continue;
@@ -314,10 +338,9 @@  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_zero(dev_hdr, struct cui_opt_data);
 	cod->name = talloc_strdup(dev_hdr, opt->device_id);
 	cod->dev = dev;
-	cod->opt = NULL;
 	dev_hdr->data = cod;
 
 	pb_debug("%s: returning %s\n",__func__,cod->name);
diff --git a/ui/ncurses/nc-menu.h b/ui/ncurses/nc-menu.h
index 136bf66..54f83af 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);
diff --git a/ui/ncurses/nc-plugin-help.c b/ui/ncurses/nc-plugin-help.c
new file mode 100644
index 0000000..4af3e10
--- /dev/null
+++ b/ui/ncurses/nc-plugin-help.c
@@ -0,0 +1,7 @@ 
+#include "nc-helpscreen.h"
+
+struct help_text plugin_help_text = define_help_text("\
+This screen lists the details and available commands of an installed plugin.\n"
+"To run a plugin command choose it in the list and select the \"Run\" button. \
+The Petitboot UI will temporarily exit to run the command, then return to \
+this screen.");
diff --git a/ui/ncurses/nc-plugin-menu-help.c b/ui/ncurses/nc-plugin-menu-help.c
new file mode 100644
index 0000000..3281e33
--- /dev/null
+++ b/ui/ncurses/nc-plugin-menu-help.c
@@ -0,0 +1,7 @@ 
+#include "nc-helpscreen.h"
+
+struct help_text plugin_menu_help_text = define_help_text("\
+Plugins discovered by Petitboot are listed in this menu.\n"
+"Press Enter to install the selected plugin. Once installed the plugin details \
+can be seen by pressing 'e'. From the detailed view plugin commands can also \
+be run.");
diff --git a/ui/ncurses/nc-plugin.c b/ui/ncurses/nc-plugin.c
new file mode 100644
index 0000000..ad8210f
--- /dev/null
+++ b/ui/ncurses/nc-plugin.c
@@ -0,0 +1,418 @@ 
+/*
+ *  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 void plugin_screen_draw(struct plugin_screen *screen,
+		struct pmenu_item *item);
+
+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;
+
+	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;
+	char *cmd;
+	int i, result;
+
+	i = widget_select_get_value(screen->widgets.commands_f);
+	/* pb-plugin copies all executables to the wrapper directory */
+	cmd = talloc_asprintf(screen, "%s/%s", "/var/lib/pb-plugins/bin",
+			basename(screen->opt->executables[i]));
+
+	if (!cmd) {
+		pb_log("nc-plugin: plugin option has missing command %d\n", i);
+		return;
+	}
+
+	const char *argv[] = {
+		pb_system_apps.pb_exec,
+		cmd,
+		NULL
+	};
+
+	/* Drop our pad before running plugin */
+	delwin(screen->pad);
+	screen->pad = NULL;
+
+	result = cui_run_cmd(screen->cui, argv);
+
+	if (result)
+		pb_log("Failed to run plugin command %s\n", cmd);
+	else
+		nc_scr_status_printf(screen->cui->current, _("Finished: %s"), cmd);
+
+	plugin_screen_draw(screen, NULL);
+
+	talloc_free(cmd);
+}
+
+/* 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_opt_data *cod = cod_from_item(item);
+	int rc;
+
+	assert(cui->current == &cui->plugin_menu->scr);
+
+	nc_scr_status_printf(cui->current, _("Installing plugin %s"),
+			cod->pd->plugin_file);
+
+	rc = cui_send_plugin_install(cui, cod->pd->plugin_file);
+
+	if (rc) {
+		pb_log("cui_send_plugin_install failed!\n");
+		nc_scr_status_printf(cui->current,
+				_("Failed to send install request"));
+	} else
+		pb_debug("cui_send_plugin_install sent!\n");
+
+	return rc;
+}
+
+static void plugin_screen_setup_widgets(struct plugin_screen *screen)
+{
+	const struct plugin_option *opt = screen->opt;
+	struct nc_widgetset *set = screen->widgetset;
+	unsigned int i;
+
+	build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
+			== N_FIELDS);
+
+	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,
+				basename(opt->executables[i]), i == 0);
+	}
+
+	screen->widgets.run_b = widget_new_button(set, 0, 0, 30,
+			_("Run selected command"), plugin_run_command, screen);
+}
+
+static int layout_pair(struct plugin_screen *screen, int y,
+		struct nc_widget_label *label,
+		struct nc_widget *field)
+{
+	struct nc_widget *label_w = widget_label_base(label);
+	widget_move(label_w, y, screen->label_x);
+	widget_move(field, y, screen->field_x);
+	return max(widget_height(label_w), widget_height(field));
+}
+
+static void plugin_screen_layout_widgets(struct plugin_screen *screen)
+{
+	unsigned int y;
+
+	/* list of details (static) */
+
+	y = 1;
+
+	layout_pair(screen, y++, screen->widgets.id_l,
+			widget_label_base(screen->widgets.id_f));
+	layout_pair(screen, y++, screen->widgets.name_l,
+			widget_label_base(screen->widgets.name_f));
+	layout_pair(screen, y++, screen->widgets.vendor_l,
+			widget_label_base(screen->widgets.vendor_f));
+	layout_pair(screen, y++, screen->widgets.vendor_id_l,
+			widget_label_base(screen->widgets.vendor_id_f));
+	layout_pair(screen, y++, screen->widgets.version_l,
+			widget_label_base(screen->widgets.version_f));
+	layout_pair(screen, y++, screen->widgets.date_l,
+			widget_label_base(screen->widgets.date_f));
+
+	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 void plugin_screen_draw(struct plugin_screen *screen,
+		struct pmenu_item *item)
+{
+	struct cui_opt_data *cod;
+	int height = N_FIELDS * 2;
+	bool repost = false;
+
+	if (item) {
+		/* First init or update */
+		cod = cod_from_item(item);
+		screen->opt = cod->pd->opt;
+	}
+
+	if (!screen->pad || getmaxy(screen->pad) < height) {
+		if (screen->pad)
+			delwin(screen->pad);
+		screen->pad = newpad(height, COLS);
+	}
+
+	if (screen->widgetset) {
+		widgetset_unpost(screen->widgetset);
+		talloc_free(screen->widgetset);
+		repost = true;
+	}
+
+	screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
+			screen->pad);
+	widgetset_set_widget_focus(screen->widgetset,
+			plugin_screen_widget_focus, screen);
+
+	plugin_screen_setup_widgets(screen);
+	plugin_screen_layout_widgets(screen);
+
+	if (repost)
+		widgetset_post(screen->widgetset);
+}
+
+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->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_draw(screen, item);
+
+	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,
 };