--- a/rules.mk
+++ b/rules.mk
@@ -40,7 +40,7 @@ discover_objs = discover/udev.o discover
 
 # client objs
 ui_common_objs = ui/common/discover-client.o ui/common/menu.o
-ncurses_objs =
+ncurses_objs =  ui/ncurses/pb-ncurses.o
 twin_objs = ui/twin/pb-twin.o
 
 # headers
--- /dev/null
+++ b/ui/ncurses/pb-ncurses.c
@@ -0,0 +1,401 @@
+/*
+ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
+ *  Copyright 2009 Sony Corp.
+ *
+ *  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
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "talloc/talloc.h"
+#include "waiter/waiter.h"
+#include "ui/common/discover-client.h"
+#include "pb-ncurses.h"
+
+#define DBG(fmt, args...) pb_log("DBG: " fmt, ## args)
+#define DBGS(fmt, args...) pb_log("DBG:%s:%d: " fmt, __func__, __LINE__, ## args)
+
+static void cui_menu_draw_start(const struct menu __attribute__((unused)) *menu)
+{
+	erase();
+}
+
+static void cui_menu_draw_item(const struct menu __attribute__((unused)) *menu,
+	const struct menu_item *i, int y)
+{
+	if (i->state == state_selected) {
+		attron(A_REVERSE);
+		mvprintw(y, 0, i->text);
+		attroff(A_REVERSE);
+	} else
+		mvprintw(y, 0, i->text);
+}
+
+static void cui_menu_draw_end(const struct menu __attribute__((unused)) *menu)
+{
+	refresh();
+}
+
+static void cui_menu_end(void)
+{
+	clear();
+	refresh();
+	endwin();
+}
+
+/**
+ * cui_menu_exit_cb - A generic program exit callback.
+ *
+ * Sets cui.abort, which causes the cui_run() routine to return.
+ */
+
+int cui_menu_exit_cb(void *arg)
+{
+	struct cui *cui = to_cui(arg);
+
+	cui->abort = 1;
+	cui_menu_end();
+	return 0;
+}
+
+/**
+ * cui_menu_insert - Dynamically insert a menu_item into the menu.
+ *
+ * Inserts the item between cui.insert_start and cui.insert_end.
+ * FIXME: need a user supplied compare rouine for sorting.
+ */
+
+static void cui_menu_insert(struct menu *menu, struct menu_item *i)
+{
+	struct menu_item *prev = menu->insert_start;
+
+	DBGS(" -> %s\n", i->text);
+	DBGS("insert_start %p\n", menu->insert_start);
+	DBGS("insert_end   %p\n", menu->insert_end);
+	DBGS("i            %p\n", i);
+
+	if (menu->insert_start == menu->insert_end)
+		menu->insert_start->state = state_hidden;
+
+	list_continue_each_entry(&menu->items, prev, list) {
+		// FIXME: need some better logic here.
+		if (prev == menu->insert_end)
+			break;
+	}
+
+	list_insert_after(&prev->list, &i->list);
+	menu->insert_end = i;
+	DBGS("insert_end   %p\n", menu->insert_end);
+	DBGS(" <- %s\n", i->text);
+}
+
+/**
+ * cui_menu_remove - Dynamically remove a menu_item from the menu.
+ *
+ * Only items inserted with cui_menu_insert can be removed.  It is an error if
+ * the menu_item is not between cui.insert_start and cui.insert_end.
+ */
+
+static void cui_menu_remove(struct menu *menu, struct menu_item *i)
+{
+	struct menu_item *prev = NULL;
+	struct menu_item *j = menu->insert_start;
+
+	// FIXME need to test
+
+	DBGS(" -> %s\n", i->text);
+
+	list_continue_each_entry(&menu->items, j, list) {
+		if (i == j)
+			break;
+		assert(j != menu->insert_end);
+		prev = j;
+	}
+
+	assert(prev);
+
+	DBGS("insert_start %p\n", menu->insert_start);
+	DBGS("insert_end   %p\n", menu->insert_end);
+	DBGS("prev         %p\n", prev);
+	DBGS("i            %p\n", i);
+
+	if (i == menu->insert_end)
+		menu->insert_end = prev;
+
+	list_remove(&i->list);
+
+	if (menu->insert_start == menu->insert_end)
+		menu->insert_start->state = state_visable;
+
+	DBGS("insert_end   %p\n", menu->insert_end);
+	DBGS(" <- %s\n", i->text);
+}
+
+/**
+ * cui_menu_set_current - Set the currently active menu and redraw it.
+ */
+
+struct menu *cui_menu_set_current(struct cui *cui, struct menu *new)
+{
+	struct menu *old = cui->current;
+
+	assert(new->m_sig == menu_sig);
+	assert(cui->current != new);
+
+	cui->current = new;
+	menu_draw(cui->current);
+
+	return old;
+}
+
+/**
+ * cui_client_device_add - Client device_add callback.
+ *
+ * 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.
+ */
+
+static int cui_client_device_add(const struct device *device, void *arg)
+{
+	struct cui *cui = to_cui(arg);
+	const struct boot_option *opt;
+
+	DBGS("-> %s\n", device->name);
+
+	list_for_each_entry(&device->boot_options, opt, list) {
+		struct menu_item *i = menu_item_init(cui->main, type_kexec,
+			state_visable, NULL);
+
+		i->text = talloc_asprintf(i, " %s: %s ", opt->name,
+			opt->description);
+		i->status = talloc_asprintf(i,
+			" kexec: image='%s' initrd='%s' args='%s'",
+			opt->boot_image_file,
+			opt->initrd_file,
+			opt->boot_args);
+		i->kexec.dev = device;
+		i->kexec.opt = opt;
+
+		cui_menu_insert(cui->main, i);
+	}
+
+	if (cui->main == cui->current)
+		menu_draw(cui->main);
+
+	return 0;
+}
+
+/**
+ * cui_client_device_remove - Client device_remov callback.
+ *
+ * Removes all the menu_items for the device from the main menu and redraws the
+ * main menu if it is active.
+ */
+
+static void cui_client_device_remove(const struct device *device, void *arg)
+{
+	struct cui *cui = to_cui(arg);
+	struct menu_item *i;
+
+	DBGS("-> %s\n", device->name);
+
+	list_for_each_entry(&cui->main->items, i, list)
+		if (pb_protocol_device_cmp(i->kexec.dev, device)) {
+			cui_menu_remove(cui->main, i);
+			talloc_free(i);
+		}
+
+	if (cui->current == cui->main)
+		menu_draw(cui->main);
+}
+
+/**
+ * cui_client_device_remove - Client device_remov callback.
+ *
+ * Removes all the menu_items for the device from the main menu and redraws the
+ * main menu if it is active.
+ */
+
+static int cui_client_process_socket(void *arg)
+{
+	struct discover_client *client = arg;
+
+	discover_client_process(client);
+	return 0;
+}
+
+/**
+ * cui_debug_remove_device - FIXME for debug only!
+ */
+
+static void cui_debug_remove_device(struct cui* cui)
+{
+	cui_client_device_remove(cui->main->insert_end->kexec.dev, cui);
+}
+
+/**
+ * cui_process_key - Process a user keystroke.
+ */
+
+static int cui_process_key(void *arg)
+{
+	struct cui *cui = to_cui(arg);
+	int c = getch();
+
+	switch(c) {
+	default:
+	case ERR:
+		break;
+	case 'D': // for debug.
+		cui_debug_remove_device(cui);
+		break;
+	case KEY_LEFT:
+	case 'E':
+	case 'e':
+		//edit...
+		break;
+	case KEY_UP:
+		menu_key_up(cui->current);
+		break;
+	case KEY_DOWN:
+	case '\t':
+		menu_key_down(cui->current);
+		break;
+	case '\n':
+	case '\r':
+		return menu_key_execute(cui->current);
+	}
+
+	return 0;
+}
+
+static struct discover_client_ops cui_client_ops = {
+	.add_device = cui_client_device_add,
+	.remove_device = cui_client_device_remove,
+};
+
+/**
+ * cui_init - Setup the cui instance.
+ * @ctx: A talloc object to use as context, or NULL for no context.
+ *
+ * Returns a pointer to a struct cui on success, or NULL on error.
+ *
+ * Allocates the cui instance, sets up the client and stdin waiters, and
+ * sets up the ncurses menu screen.
+ */
+
+struct cui *cui_init(void* ctx)
+{
+	struct cui *cui;
+	struct discover_client *client;
+
+	atexit(cui_menu_end);
+
+	cui = talloc_zero(ctx, struct cui);
+
+	if (!cui) {
+		pb_log("%s: alloc cui failed.\n", __func__);
+		fprintf(stderr, "%s: alloc cui failed.\n", __func__);
+		goto fail_alloc;
+	}
+
+	cui->c_sig = cui_sig;
+
+	client = discover_client_init(&cui_client_ops, cui);
+
+	if (!client) {
+		pb_log("%s: discover_client_init failed.\n", __func__);
+		fprintf(stderr, "%s: error: discover_client_init failed.\n",
+			__func__);
+		fprintf(stderr, "check that pb-discover, "
+			"the petitboot daemon is running.\n");
+		goto fail_client_init;
+	}
+
+	initscr();			/* Initialize ncurses. */
+	cbreak();			/* Disable line buffering. */
+	noecho();			/* Disable getch() echo. */
+	nonl();				/* Disable new-line translation. */
+	intrflush(stdscr, FALSE);	/* Disable interrupt flush. FIXME: need it? */
+	keypad(stdscr, TRUE);		/* Enable num keypad keys. */
+	curs_set(0);			/* Make cursor invisible */
+	nodelay(stdscr, TRUE);		/* Enable non-blocking getch() */
+
+	waiter_register(discover_client_get_fd(client), WAIT_IN,
+		cui_client_process_socket, client);
+
+	waiter_register(STDIN_FILENO, WAIT_IN, cui_process_key, cui);
+
+	return cui;
+
+fail_client_init:
+	talloc_free(cui);
+fail_alloc:
+	return NULL;
+}
+
+/**
+ * cui_menu_init - Setup a menu to use with the cui engine.
+ */
+
+struct menu *cui_menu_init(struct cui *cui)
+{
+	struct menu *m = menu_init(cui);
+
+	if (!m) {
+		pb_log("%s: menu_init failed\n", __func__);
+		return NULL;
+	}
+
+	m->draw_start = cui_menu_draw_start;
+	m->draw_item = cui_menu_draw_item;
+	m->draw_end = cui_menu_draw_end;
+
+	return m;
+}
+
+/**
+ * cui_run - The main cui program loop.
+ * @cui: The cui instance.
+ * @main: The menu to use as the main menu.
+ * @current: Optional agrument to indicate the menu to use as the active menu.
+ * on stattup.  Can be NULL to default to the @main menu.
+ *
+ * Runs the cui engine.  Does not return until indicated to do so by some
+ * user action, or an error occurs.  Frees the cui object on return.
+ */
+
+void cui_run(struct cui *cui, struct menu *main, struct menu *current)
+{
+	assert(main);
+	assert(main->m_sig == menu_sig);
+
+	cui->main = main;
+
+	if (current) {
+		assert(current->m_sig == menu_sig);
+		cui->current = current;
+	} else
+		cui->current = main;
+
+	menu_draw(cui->current);
+
+	while (!cui->abort && !waiter_poll())
+		(void)0;
+
+	talloc_free(cui);
+}
--- /dev/null
+++ b/ui/ncurses/pb-ncurses.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
+ *  Copyright 2009 Sony Corp.
+ *
+ *  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(_PB_NCURSES_H)
+#define _PB_NCURSES_H
+
+#include <ncurses.h>
+
+#include "log/log.h"
+#include "ui/common/menu.h"
+
+#if defined(__cplusplus)
+# define EXTERN_C extern "C"
+#else
+# define EXTERN_C
+#endif
+
+enum {cui_sig = 333,};
+
+/**
+ * struct cui - Data structure defining a cui state machine.
+ * @c_sig: Signature for callback type checking, should be cui_sig.
+ * @abort: When set to true signals the state machine to exit.
+ * @current: Pointer to the user specified active menu.
+ * @main: Pointer to the user supplied main menu.
+ *
+ * Device boot_options are dynamically added and removed from the @main
+ * menu.
+ */
+
+struct cui {
+	int c_sig;
+	volatile int abort;
+	struct menu* current;
+	struct menu* main;
+};
+
+#define to_cui(_arg) \
+	(assert(((struct cui *)(_arg))->c_sig == cui_sig), \
+	((struct cui *)(_arg)))
+
+EXTERN_C struct cui *cui_init(void* ctx);
+EXTERN_C struct menu *cui_menu_init(struct cui *cui);
+EXTERN_C struct menu *cui_menu_set_current(struct cui *cui,
+	struct menu *new);
+EXTERN_C void cui_run(struct cui *cui, struct menu *main, struct menu *current);
+
+/* generic cui menu callbacks */
+
+EXTERN_C int cui_menu_exit_cb(void *arg);
+
+#endif
