[08/10] ui/ncurses: "Firmware Updates" screen

Message ID 20170825055940.24134-9-sam@mendozajonas.com
State New
Headers show

Commit Message

Samuel Mendoza-Jonas Aug. 25, 2017, 5:59 a.m.
The new "Firmware Updates" screen provides an interface for the user to
specify where to retrieve metadata information from, optionally specify
a particular update source, visually compare firmware versions, and
execute an update.

If the platform does not support updates or updates are otherwise
disabled, this screen is empty aside from an informational message.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
---
 ui/common/discover-client.c   |  18 ++
 ui/common/discover-client.h   |   4 +
 ui/ncurses/Makefile.am        |   5 +-
 ui/ncurses/nc-cui.c           |  87 ++++++-
 ui/ncurses/nc-cui.h           |   3 +
 ui/ncurses/nc-firmware-help.c |   4 +
 ui/ncurses/nc-firmware.c      | 553 ++++++++++++++++++++++++++++++++++++++++++
 ui/ncurses/nc-firmware.h      |  34 +++
 ui/ncurses/nc-scr.h           |   1 +
 ui/ncurses/nc-widgets.c       |   9 +-
 ui/ncurses/nc-widgets.h       |   1 +
 11 files changed, 715 insertions(+), 4 deletions(-)
 create mode 100644 ui/ncurses/nc-firmware-help.c
 create mode 100644 ui/ncurses/nc-firmware.c
 create mode 100644 ui/ncurses/nc-firmware.h

Patch

diff --git a/ui/common/discover-client.c b/ui/common/discover-client.c
index 8ad4611..ed51afc 100644
--- a/ui/common/discover-client.c
+++ b/ui/common/discover-client.c
@@ -452,3 +452,21 @@  int discover_client_send_plugin_install(struct discover_client *client,
 
 	return pb_protocol_write_message(client->fd, message);
 }
+
+int discover_client_send_firmware_update(struct discover_client *client,
+		char *url)
+{
+	struct pb_protocol_message *message;
+	int len;
+
+	len = pb_protocol_url_len(url);
+
+	message = pb_protocol_create_message(client,
+				PB_PROTOCOL_ACTION_FIRMWARE_UPDATE, len);
+	if (!message)
+		return -1;
+
+	pb_protocol_serialise_url(url, 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 7224691..9352de1 100644
--- a/ui/common/discover-client.h
+++ b/ui/common/discover-client.h
@@ -102,4 +102,8 @@  int discover_client_send_url(struct discover_client *client, char *url);
 int discover_client_send_plugin_install(struct discover_client *client,
 		char *file);
 
+/* Send url to firmware image to the discover server */
+int discover_client_send_firmware_update(struct discover_client *client,
+		char *url);
+
 #endif
diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am
index 40e11b8..4418961 100644
--- a/ui/ncurses/Makefile.am
+++ b/ui/ncurses/Makefile.am
@@ -55,7 +55,10 @@  ui_ncurses_libpbnc_la_SOURCES = \
 	ui/ncurses/nc-plugin.c \
 	ui/ncurses/nc-plugin.h \
 	ui/ncurses/nc-plugin-help.c \
-	ui/ncurses/nc-plugin-menu-help.c
+	ui/ncurses/nc-plugin-menu-help.c \
+	ui/ncurses/nc-firmware.c \
+	ui/ncurses/nc-firmware.h \
+	ui/ncurses/nc-firmware-help.c
 
 sbin_PROGRAMS += ui/ncurses/petitboot-nc
 
diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
index 46b7839..835cef3 100644
--- a/ui/ncurses/nc-cui.c
+++ b/ui/ncurses/nc-cui.c
@@ -45,6 +45,7 @@ 
 #include "nc-statuslog.h"
 #include "nc-subset.h"
 #include "nc-plugin.h"
+#include "nc-firmware.h"
 
 extern const struct help_text main_menu_help_text;
 extern const struct help_text plugin_menu_help_text;
@@ -392,6 +393,13 @@  static void cui_plugin_menu_exit(struct pmenu *menu)
 	cui_set_current(cui, &cui->main->scr);
 }
 
+static void cui_firmware_exit(struct cui *cui)
+{
+	cui_set_current(cui, &cui->main->scr);
+	talloc_free(cui->firmware_screen);
+	cui->firmware_screen = NULL;
+}
+
 void cui_show_add_url(struct cui *cui)
 {
 	cui->add_url_screen = add_url_screen_init(cui, cui_add_url_exit);
@@ -410,6 +418,13 @@  void cui_show_plugin(struct pmenu_item *item)
 	cui_set_current(cui, plugin_screen_scr(cui->plugin_screen));
 }
 
+void cui_show_firmware(struct cui *cui)
+{
+	cui->firmware_screen = firmware_screen_init(cui, cui->config,
+			cui->sysinfo, cui_firmware_exit);
+	cui_set_current(cui, firmware_screen_scr(cui->firmware_screen));
+}
+
 static void cui_help_exit(struct cui *cui)
 {
 	cui_set_current(cui, help_screen_return_scr(cui->help_screen));
@@ -1018,6 +1033,44 @@  static void cui_update_mm_title(struct cui *cui)
 		nc_scr_post(cui->current);
 }
 
+static void cui_update_menu_item_name(struct cui *cui, const char *prefix,
+		const char *suffix)
+{
+	struct pmenu_item *item;
+	bool found = false;
+	unsigned int i;
+	char *label;
+
+	if (cui->current == &cui->main->scr)
+		nc_scr_unpost(cui->current);
+
+	if (set_menu_items(cui->main->ncm, NULL)) {
+		pb_log("%s: failed to unset menu items\n", __func__);
+		return;
+	}
+
+	for (i = 0 ; i < cui->main->item_count; i++) {
+		item = item_userptr(cui->main->items[i]);
+		if (strncmp(item->nci->name.str, prefix, strlen(prefix)))
+			continue;
+		label = talloc_asprintf(item, "%s %s", prefix, suffix);
+		pmenu_item_update(item, label);
+		talloc_free(label);
+		found = true;
+		break;
+	}
+
+	if (!found)
+		pb_log("Could not find item '%s' to apply suffix '%s'\n",
+				prefix, suffix);
+
+	if (set_menu_items(cui->main->ncm, cui->main->items))
+		pb_log("%s: failed to set menu items\n", __func__);
+
+	if (cui->current == &cui->main->scr)
+		nc_scr_post(cui->current);
+}
+
 static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
 {
 	struct cui *cui = cui_from_arg(arg);
@@ -1039,6 +1092,16 @@  static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
 	if (cui->boot_editor)
 		boot_editor_update(cui->boot_editor, sysinfo);
 
+	if (cui->firmware_screen)
+		firmware_screen_update(cui->firmware_screen, cui->config,
+				sysinfo);
+
+	/* Update the Firmware title if update occured */
+	if (sysinfo->update_status == UPDATE_COMPLETED)
+		cui_update_menu_item_name(cui, "Firmware Updates",
+				"(Update Applied!)");
+
+
 	cui_update_mm_title(cui);
 }
 
@@ -1085,6 +1148,10 @@  static void cui_update_config(struct config *config, void *arg)
 	if (cui->config_screen)
 		config_screen_update(cui->config_screen, config, cui->sysinfo);
 
+	if (cui->firmware_screen)
+		firmware_screen_update(cui->firmware_screen, config,
+				cui->sysinfo);
+
 	if (config->safe_mode)
 		nc_scr_status_printf(cui->current,
 				_("SAFE MODE: select '%s' to continue"),
@@ -1106,6 +1173,11 @@  int cui_send_plugin_install(struct cui *cui, char *file)
 	return discover_client_send_plugin_install(cui->client, file);
 }
 
+int cui_send_firmware_update(struct cui *cui, char *url)
+{
+	return discover_client_send_firmware_update(cui->client, url);
+}
+
 void cui_send_reinit(struct cui *cui)
 {
 	discover_client_send_reinit(cui->client);
@@ -1156,6 +1228,13 @@  static int menu_plugin_execute(struct pmenu_item *item)
 	return 0;
 }
 
+static int menu_firmware_execute(struct pmenu_item *item)
+{
+	if (cui_from_item(item)->client)
+		cui_show_firmware(cui_from_item(item));
+	return 0;
+}
+
 /**
  * pb_mm_init - Setup the main menu instance.
  */
@@ -1166,7 +1245,7 @@  static struct pmenu *main_menu_init(struct cui *cui)
 	int result;
 	bool lockdown = lockdown_active();
 
-	m = pmenu_init(cui, 9, cui_on_exit);
+	m = pmenu_init(cui, 10, cui_on_exit);
 	if (!m) {
 		pb_log("%s: failed\n", __func__);
 		return NULL;
@@ -1216,12 +1295,16 @@  static struct pmenu *main_menu_init(struct cui *cui)
 	i->on_execute = menu_plugin_execute;
 	pmenu_item_insert(m, i, 7);
 
+	i = pmenu_item_create(m, _("Firmware Updates"));
+	i->on_execute = menu_firmware_execute;
+	pmenu_item_insert(m, i, 8);
+
 	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, 8);
+	pmenu_item_insert(m, i, 9);
 
 	result = pmenu_setup(m);
 
diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h
index b310f4a..711c7f8 100644
--- a/ui/ncurses/nc-cui.h
+++ b/ui/ncurses/nc-cui.h
@@ -72,6 +72,7 @@  struct cui {
 	struct help_screen *help_screen;
 	struct subset_screen *subset_screen;
 	struct statuslog_screen *statuslog_screen;
+	struct firmware_screen *firmware_screen;
 	struct pjs *pjs;
 	void *platform_info;
 	unsigned int default_item;
@@ -99,6 +100,8 @@  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_show_firmware(struct cui *item);
+int cui_send_firmware_update(struct cui *cui, char *url);
 void cui_send_reinit(struct cui *cui);
 
 /* convenience routines */
diff --git a/ui/ncurses/nc-firmware-help.c b/ui/ncurses/nc-firmware-help.c
new file mode 100644
index 0000000..7b9d072
--- /dev/null
+++ b/ui/ncurses/nc-firmware-help.c
@@ -0,0 +1,4 @@ 
+#include "nc-helpscreen.h"
+
+struct help_text firmware_help_text = define_help_text("\
+Useful help text here");
diff --git a/ui/ncurses/nc-firmware.c b/ui/ncurses/nc-firmware.c
new file mode 100644
index 0000000..55fbcc9
--- /dev/null
+++ b/ui/ncurses/nc-firmware.c
@@ -0,0 +1,553 @@ 
+/*
+ *  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 <pb-config/pb-config.h>
+#include <talloc/talloc.h>
+#include <types/types.h>
+#include <i18n/i18n.h>
+#include <log/log.h>
+
+#include "nc-cui.h"
+#include "nc-firmware.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 firmware_help_text;
+
+struct package_versions {
+	struct nc_widget_label	*name;
+	struct nc_widget_label	*current;
+	struct nc_widget_label	*update;
+};
+
+struct firmware_screen {
+	struct nc_scr		scr;
+	struct cui		*cui;
+	struct nc_widgetset	*widgetset;
+	WINDOW			*pad;
+
+	bool			exit;
+	bool			show_help;
+	bool			need_redraw;
+	bool			need_update;
+	void			(*on_exit)(struct cui *);
+
+	int			label_x;
+	int			field_x;
+	int			scroll_y;
+
+	unsigned int		n_versions;
+	int		current_x;
+	int		update_x;
+
+	struct {
+		struct nc_widget_label		*source_l;
+		struct nc_widget_textbox	*source_f;
+		struct nc_widget_button		*source_update_b;
+
+		struct nc_widget_label		*custom_l;
+		struct nc_widget_textbox	*custom_f;
+		struct nc_widget_button		*custom_update_b;
+
+		struct nc_widget_label		*package_l;
+		struct nc_widget_label		*current_l;
+		struct nc_widget_label		*update_l;
+		struct package_versions		*versions;
+
+		struct nc_widget_label		*status_l;
+		struct nc_widget_label		*status_f;
+		struct nc_widget_label		*safe_mode;
+
+		struct nc_widget_button		*help_b;
+		struct nc_widget_button		*cancel_b;
+	} widgets;
+};
+
+static struct firmware_screen *firmware_screen_from_scr(struct nc_scr *scr)
+{
+	struct firmware_screen *firmware_screen;
+
+	assert(scr->sig == pb_firmware_screen_sig);
+	firmware_screen = (struct firmware_screen *)
+		((char *)scr - (size_t)&((struct firmware_screen *)0)->scr);
+	assert(firmware_screen->scr.sig == pb_firmware_screen_sig);
+	return firmware_screen;
+}
+
+struct nc_scr *firmware_screen_scr(struct firmware_screen *screen)
+{
+	return &screen->scr;
+}
+
+static void pad_refresh(struct firmware_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 firmware_screen_widget_focus(struct nc_widget *widget, void *arg)
+{
+	struct firmware_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 firmware_screen_process_key(struct nc_scr *scr, int key)
+{
+	struct firmware_screen *screen = firmware_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, _("Firmware Updates"),
+				&firmware_help_text);
+
+	} else if (handled) {
+		pad_refresh(screen);
+	}
+}
+
+static void help_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	screen->show_help = true;
+}
+
+static void cancel_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	screen->exit = true;
+}
+
+static void update_source_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	struct config *config;
+	char *url;
+	int rc;
+
+	config = config_copy(screen, screen->cui->config);
+
+	url = widget_textbox_get_value(screen->widgets.source_f);
+
+	talloc_free(config->metadata_source);
+	config->metadata_source = talloc_strdup(config, url);
+
+	config->safe_mode = false;
+
+	rc = cui_send_config(screen->cui, config);
+	talloc_free(config);
+
+	if (rc)
+		pb_log("nc-firmware: Failed to send config!\n");
+
+	screen->exit = true;
+}
+
+static void update_custom_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	char *url = widget_textbox_get_value(screen->widgets.custom_f);
+	cui_send_firmware_update(screen->cui, url);
+	screen->exit = true;
+}
+
+static void firmware_screen_setup_empty(struct firmware_screen *screen,
+		const struct config *config, const struct system_info *sysinfo)
+{
+	if (sysinfo && !sysinfo->update_support)
+		widget_new_label(screen->widgetset, 2, screen->field_x,
+				_("Firmware update not supported on this platform"));
+	else if (config && !config->platform_update)
+		widget_new_label(screen->widgetset, 2, screen->field_x,
+				_("Firmware update not enabled on this platform"));
+	else
+		widget_new_label(screen->widgetset, 2, screen->field_x,
+				_("Waiting for configuration data..."));
+	screen->widgets.cancel_b = widget_new_button(screen->widgetset,
+			4, screen->field_x, 9, _("Cancel"),
+			cancel_click, screen);
+}
+
+static void firmware_screen_setup_widgets(struct firmware_screen *screen,
+		const struct config *config, const struct system_info *sysinfo)
+{
+	struct nc_widgetset *set = screen->widgetset;
+	int len_package, len_current;
+	unsigned int i, j;
+	char *label;
+
+	build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
+			== N_FIELDS);
+
+	screen->widgets.source_l = widget_new_label(set, 0, 0,
+			_("Metadata source:"));
+	screen->widgets.source_f = widget_new_textbox(set, 0, 0,
+			COLS - screen->field_x - 10, config->metadata_source);
+
+	screen->widgets.source_update_b = widget_new_button(set, 0, 0, 40,
+			_("Set and check remote metadata file"),
+			update_source_click, screen);
+
+	screen->widgets.custom_l = widget_new_label(set, 0, 0, "Update File:");
+	screen->widgets.custom_f = widget_new_textbox(set, 0, 0,
+			COLS - screen->field_x - 10, config->update_source);
+
+	screen->widgets.custom_update_b = widget_new_button(set, 0, 0, 30,
+			_("Update firmware"),
+			update_custom_click, screen);
+
+	label = talloc_strdup(screen,
+			update_status_display_name(sysinfo->update_status));
+	screen->widgets.status_l = widget_new_label(set, 0, 0,
+			_("Update Status:"));
+	screen->widgets.status_f = widget_new_label(set, 0, 0, label);
+
+	screen->widgets.package_l = widget_new_label(set, 0, 0,
+			_("Packages"));
+	screen->widgets.current_l = widget_new_label(set, 0, 0,
+			_("Current"));
+	screen->widgets.update_l = widget_new_label(set, 0, 0,
+			_("Update"));
+	screen->widgets.versions = talloc_array(screen,
+			struct package_versions, sysinfo->n_primary);
+	if (!screen->widgets.versions) {
+		pb_log("Failed to alloc versions array!\n");
+		goto skip_versions;
+	}
+
+	screen->n_versions = sysinfo->n_primary;
+	len_package = len_current = 0;
+	/* Create current version package name and version labels */
+	for (i = 0; i < sysinfo->n_primary; i++) {
+		screen->widgets.versions[i].name = widget_new_label(set,
+				0, 0, sysinfo->platform_primary[i].name);
+		widget_set_selectable(widget_label_base(
+					screen->widgets.versions[i].name),
+				true);
+		screen->widgets.versions[i].current = widget_new_label(set,
+				0, 0, sysinfo->platform_primary[i].version);
+		screen->widgets.versions[i].update = NULL;
+		if (strncols(sysinfo->platform_primary[i].name) > len_package)
+			len_package = strncols(sysinfo->platform_primary[i].name);
+		if (strncols(sysinfo->platform_primary[i].version) > len_current)
+			len_current = strncols(sysinfo->platform_primary[i].version);
+	}
+
+	screen->current_x = screen->label_x + len_package + 2;
+	screen->update_x += screen->current_x + len_current + 2;
+
+	/* Create update version labels, checking for missing packages */
+	for (i = 0; i < sysinfo->n_new; i++) {
+		for (j = 0; j < sysinfo->n_primary; j++) {
+			if ((strncmp(sysinfo->platform_primary[j].name,
+					sysinfo->platform_new[i].name,
+					strlen(sysinfo->platform_new[i].name)) == 0) &&
+					(strlen(sysinfo->platform_primary[j].name) ==
+					strlen(sysinfo->platform_new[i].name)))
+				break;
+		}
+		if (j < sysinfo->n_primary) {
+			/* Corresponding current exists */
+			screen->widgets.versions[j].update = widget_new_label(set,
+					0, 0, sysinfo->platform_new[i].version);
+			continue;
+		}
+
+		/* Package exists only in update versions */
+		screen->widgets.versions = talloc_realloc(screen,
+				screen->widgets.versions,
+				struct package_versions,
+				j + 1);
+		if (!screen->widgets.versions) {
+			pb_log("Failed to alloc new update package!\n");
+			break;
+		}
+		screen->n_versions++;
+		screen->widgets.versions[j].name = widget_new_label(set,
+				0, 0, sysinfo->platform_new[i].name);
+		widget_set_selectable(widget_label_base(
+					screen->widgets.versions[j].name),
+				true);
+		screen->widgets.versions[j].current = NULL;
+		screen->widgets.versions[j].update = widget_new_label(set,
+				0, 0, sysinfo->platform_new[i].version);
+	}
+
+skip_versions:
+	if (config->safe_mode)
+		screen->widgets.safe_mode = widget_new_label(set, 0, 0,
+			 _("Selecting either option will exit safe mode"));
+
+	screen->widgets.help_b = widget_new_button(set, 0, 0, 10,
+			_("Help"), help_click, screen);
+	screen->widgets.cancel_b = widget_new_button(set, 0, 0, 10,
+			_("Cancel"), cancel_click, screen);
+}
+
+static int layout_pair(struct firmware_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 firmware_screen_layout_widgets(struct firmware_screen *screen)
+{
+	unsigned int y, i;
+
+	y = 1;
+
+	y += layout_pair(screen, y, screen->widgets.source_l,
+			widget_textbox_base(screen->widgets.source_f));
+	widget_move(widget_button_base(screen->widgets.source_update_b), y++,
+			screen->field_x);
+
+	y +=1 ;
+
+	y += layout_pair(screen, y, screen->widgets.custom_l,
+			widget_textbox_base(screen->widgets.custom_f));
+	widget_move(widget_button_base(screen->widgets.custom_update_b), y++,
+			screen->field_x);
+
+	y +=1 ;
+
+	y += layout_pair(screen, y, screen->widgets.status_l,
+			widget_label_base(screen->widgets.status_f));
+
+	y += 1;
+
+	widget_move(widget_label_base(screen->widgets.package_l), y,
+				screen->label_x);
+	widget_move(widget_label_base(screen->widgets.current_l), y,
+				screen->current_x);
+	widget_move(widget_label_base(screen->widgets.update_l), y,
+				screen->update_x);
+
+	y += 1;
+	for (i = 0; i < screen->n_versions; i++) {
+		widget_move(widget_label_base(screen->widgets.versions[i].name),
+					y, screen->label_x);
+		if (screen->widgets.versions[i].current)
+			widget_move(widget_label_base(
+					screen->widgets.versions[i].current),
+				y, screen->current_x);
+		if (screen->widgets.versions[i].update)
+			widget_move(widget_label_base(
+					screen->widgets.versions[i].update),
+				y, screen->update_x);
+		y += 1;
+
+	}
+
+	y += 1;
+
+	if (screen->cui->config->safe_mode) {
+		widget_move(widget_label_base(screen->widgets.safe_mode),
+			y, screen->label_x);
+		widget_set_visible(widget_label_base(screen->widgets.safe_mode),
+					true);
+		y += 1;
+	}
+
+	y += 1;
+
+	widget_move(widget_button_base(screen->widgets.help_b), y,
+		screen->field_x);
+	widget_move(widget_button_base(screen->widgets.cancel_b), y,
+		screen->field_x + 20);
+
+	y += 1;
+}
+
+static void firmware_screen_draw(struct firmware_screen *screen,
+		const struct config *config, const struct system_info *sysinfo)
+{
+	bool repost = false;
+	int height;
+
+	height = N_FIELDS + 6 + (sysinfo->n_primary + 2) + (sysinfo->n_new + 2);
+	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,
+			firmware_screen_widget_focus, screen);
+
+	if (!config || !sysinfo || !sysinfo->update_support ||
+			!config->platform_update) {
+		firmware_screen_setup_empty(screen, config, sysinfo);
+	} else {
+		firmware_screen_setup_widgets(screen, config, sysinfo);
+		firmware_screen_layout_widgets(screen);
+	}
+
+	if (repost)
+		widgetset_post(screen->widgetset);
+}
+
+void firmware_screen_update(struct firmware_screen *screen,
+		const struct config *config, const struct system_info *sysinfo)
+{
+	if (screen->cui->current != firmware_screen_scr(screen)) {
+		screen->need_update = true;
+		return;
+	}
+
+	firmware_screen_draw(screen, config, sysinfo);
+	pad_refresh(screen);
+}
+
+static int firmware_screen_post(struct nc_scr *scr)
+{
+	struct firmware_screen *screen = firmware_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 firmware_screen_unpost(struct nc_scr *scr)
+{
+	widgetset_unpost(firmware_screen_from_scr(scr)->widgetset);
+	return 0;
+}
+
+static void firmware_screen_resize(struct nc_scr *scr)
+{
+	/* FIXME: forms can't be resized, need to recreate here */
+	firmware_screen_unpost(scr);
+	firmware_screen_post(scr);
+}
+
+static int firmware_screen_destroy(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	if (screen->pad)
+		delwin(screen->pad);
+	return 0;
+}
+
+struct firmware_screen *firmware_screen_init(struct cui *cui,
+		const struct config *config,
+		const struct system_info *sysinfo,
+		void (*on_exit)(struct cui *))
+{
+	struct firmware_screen *screen = talloc_zero(cui, struct firmware_screen);
+	talloc_set_destructor(screen, firmware_screen_destroy);
+
+	nc_scr_init(&screen->scr, pb_firmware_screen_sig, 0,
+			cui, firmware_screen_process_key,
+			firmware_screen_post, firmware_screen_unpost,
+			firmware_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,
+			_("Firmware Updates"));
+	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);
+
+	firmware_screen_draw(screen, config, sysinfo);
+
+	return screen;
+}
+
diff --git a/ui/ncurses/nc-firmware.h b/ui/ncurses/nc-firmware.h
new file mode 100644
index 0000000..38891c4
--- /dev/null
+++ b/ui/ncurses/nc-firmware.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_FIRMWARE_H
+#define _NC_FIRMWARE_H
+
+#include "nc-cui.h"
+
+struct firmware_screen;
+
+struct firmware_screen *firmware_screen_init(struct cui *cui,
+		const struct config *config,
+		const struct system_info *sysinfo,
+                void (*on_exit)(struct cui *));
+
+struct nc_scr *firmware_screen_scr(struct firmware_screen *screen);
+void firmware_screen_update(struct firmware_screen *screen,
+		const struct config *config, const struct system_info *sysinfo);
+
+#endif /* defined _NC_FIRMWARE_H */
diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h
index 5671a6b..35ede11 100644
--- a/ui/ncurses/nc-scr.h
+++ b/ui/ncurses/nc-scr.h
@@ -50,6 +50,7 @@  enum pb_nc_sig {
 	pb_add_url_screen_sig	= 888,
 	pb_subset_screen_sig	= 101,
 	pb_plugin_screen_sig	= 202,
+	pb_firmware_screen_sig	= 303,
 	pb_removed_sig		= -999,
 };
 
diff --git a/ui/ncurses/nc-widgets.c b/ui/ncurses/nc-widgets.c
index 15cec80..7a318cb 100644
--- a/ui/ncurses/nc-widgets.c
+++ b/ui/ncurses/nc-widgets.c
@@ -194,7 +194,6 @@  static int label_destructor(void *ptr)
 	return 0;
 }
 
-
 struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
 		int y, int x, char *str)
 {
@@ -1337,3 +1336,11 @@  int widget_focus_y(struct nc_widget *widget)
 	return widget->focus_y;
 }
 
+void widget_set_selectable(struct nc_widget *widget, bool selectable)
+{
+	if (selectable)
+		field_opts_on(widget->field, O_ACTIVE);
+	else
+		field_opts_off(widget->field, O_ACTIVE);
+}
+
diff --git a/ui/ncurses/nc-widgets.h b/ui/ncurses/nc-widgets.h
index 4b67da7..7d7e8b5 100644
--- a/ui/ncurses/nc-widgets.h
+++ b/ui/ncurses/nc-widgets.h
@@ -86,6 +86,7 @@  int widget_width(struct nc_widget *widget);
 int widget_y(struct nc_widget *widget);
 int widget_x(struct nc_widget *widget);
 int widget_focus_y(struct nc_widget *widget);
+void widget_set_selectable(struct nc_widget *widget, bool selectable);
 
 /* widgetset API */
 typedef void (*widget_focus_cb)(struct nc_widget *widget, void *arg);