a work-in-progress

Subject: Update PS3 twin GUI program

Update the PS3 twin GUI program to work with petitboot-multi-ui.

Signed-off-by: Geoff Levand <geoffrey.levand@am.sony.com>
---
 Makefile.in        |    1 
 configure.ac       |   12 
 rules.mk           |   14 
 ui/twin/ps3-twin.c | 1441 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1463 insertions(+), 5 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -18,6 +18,7 @@ twin_LDFLAGS = @twin_LIBS@
 
 # build target
 ENABLE_PS3 = @ENABLE_PS3@
+ENABLE_X11 = @ENABLE_X11@
 
 # other programs
 INSTALL = @INSTALL@
--- a/configure.ac
+++ b/configure.ac
@@ -56,7 +56,17 @@ AS_IF([test "x$with_twin" != xno],
 		[if test "x$with_twin" != xcheck; then
 			AC_MSG_FAILURE([--with-twin was given, but test for twin failed])
 		fi],
-		[${twin_LIBS}])])
+		[${twin_LIBS}])
+	AC_CHECK_HEADERS([libtwin/twin_x11.h])])
+
+AC_ARG_ENABLE([x11],
+	[AS_HELP_STRING([--enable-x11],
+		[build for x11])],
+	[],
+	[enable_x11=check])
+
+AS_IF([test "x$enable_x11" != xno], [AC_SUBST([ENABLE_X11], ["y"])], [])
+
 
 mkdir -p discover lib/list lib/log lib/pb-protocol lib/system lib/talloc \
 	lib/waiter test ui/common ui/ncurses ui/test ui/twin utils
--- a/rules.mk
+++ b/rules.mk
@@ -54,7 +54,7 @@ ui_common_objs = ui/common/discover-clie
 	ui/common/url.o
 ncurses_objs = ui/ncurses/nc-scr.o ui/ncurses/nc-menu.o ui/ncurses/nc-ked.o \
 	ui/ncurses/nc-cui.o
-twin_objs = ui/twin/pb-twin.o
+twin_objs =
 
 # Makefiles
 makefiles = Makefile $(top_srcdir)/rules.mk
@@ -89,11 +89,17 @@ $(pb_test): $(pb_test_objs)
 	$(LINK.o) -o $@ $^
 
 # twin gui
-pb_twin_objs = $(client_objs) $(twin_objs) ui/twin/ps3-twin.o
+pb_twin_objs-y$(ENABLE_PS3) += ui/twin/pb-twin.o
+pb_twin_objs-$(ENABLE_PS3) += ui/twin/ps3-twin.o ui/common/ps3.o
+pb_twin_cflags-$(ENABLE_X11) += -DUSE_X11
+pb_twin_ldflags-$(ENABLE_PS3) += -lps3-utils
+
+pb_twin_objs = $(client_objs) $(twin_objs)  $(pb_twin_objs-y)
 $(pb_twin_objs): $(makefiles)
 
-$(pb_twin): LDFLAGS+=$(twin_LDFLAGS) $(LIBTWIN)
-$(pb_twin): CFLAGS+=$(twin_CFLAGS)
+$(pb_twin): LDFLAGS += $(pb_twin_ldflags-y) $(twin_LDFLAGS) $(LIBTWIN)
+$(pb_twin): CFLAGS += $(pb_twin_cflags-y) $(twin_CFLAGS) \
+	-DPB_ARTWORK_PATH='"$(pkgdatadir)/artwork/"'
 
 $(pb_twin): $(pb_twin_objs)
 	$(LINK.o) -o $@ $^
--- /dev/null
+++ b/ui/twin/ps3-twin.c
@@ -0,0 +1,1441 @@
+/*
+ * Petitboot twin bootloader for the PS3 game console
+ *
+ *  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(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <libtwin/twin.h>
+#include <libtwin/twin_fbdev.h>
+#include <libtwin/twin_jpeg.h>
+#include <libtwin/twin_linux_mouse.h>
+#include <libtwin/twin_linux_js.h>
+#include <libtwin/twin_png.h>
+#if defined(HAVE_LIBTWIN_TWIN_X11_H)
+# include <libtwin/twin_x11.h>
+#endif
+#include <linux/input.h>
+
+#include "log/log.h"
+#include "talloc/talloc.h"
+#include "waiter/waiter.h"
+#include "ui/common/discover-client.h"
+#include "ui/common/ps3.h"
+#include "ui/common/timer.h"
+
+//------------------------------------------------------------------------------
+// twin-ui.c/h
+
+#define DBG(fmt, args...) pb_log("DBG: " fmt, ## args)
+#define DBGS(fmt, args...) \
+	pb_log("DBG:%s:%d: " fmt, __func__, __LINE__, ## args)
+
+/* control to keyboard mappings for the sixaxis controller */
+uint8_t sixaxis_map[] = {
+	0,		/*   0  Select		*/
+	0,		/*   1  L3              */
+	0,		/*   2  R3              */
+	0,		/*   3  Start           */
+	KEY_UP,		/*   4  Dpad Up         */
+	KEY_RIGHT,	/*   5  Dpad Right      */
+	KEY_DOWN,	/*   6  Dpad Down       */
+	KEY_LEFT,	/*   7  Dpad Left       */
+	0,		/*   8  L2              */
+	0,		/*   9  R2              */
+	0,		/*  10  L1              */
+	0,		/*  11  R1              */
+	0,		/*  12  Triangle        */
+	KEY_ENTER,	/*  13  Circle          */
+	0,		/*  14  Cross           */
+	KEY_DELETE,	/*  15  Square          */
+	0,		/*  16  PS Button       */
+	0,		/*  17  nothing      */
+	0,		/*  18  nothing      */
+};
+
+#define PBOOT_LEFT_PANE_SIZE		160
+#define PBOOT_LEFT_PANE_COLOR		0x80000000
+#define PBOOT_LEFT_LINE_COLOR		0xff000000
+
+#define PBOOT_LEFT_FOCUS_WIDTH		80
+#define PBOOT_LEFT_FOCUS_HEIGHT		80
+#define PBOOT_LEFT_FOCUS_XOFF		40
+#define PBOOT_LEFT_FOCUS_YOFF		40
+#define PBOOT_LEFT_FOCUS_XRAD		(6 * TWIN_FIXED_ONE)
+#define PBOOT_LEFT_FOCUS_YRAD		(6 * TWIN_FIXED_ONE)
+
+#define PBOOT_RIGHT_FOCUS_XOFF		20
+#define PBOOT_RIGHT_FOCUS_YOFF		60
+#define PBOOT_RIGHT_FOCUS_HEIGHT	80
+#define PBOOT_RIGHT_FOCUS_XRAD		(6 * TWIN_FIXED_ONE)
+#define PBOOT_RIGHT_FOCUS_YRAD		(6 * TWIN_FIXED_ONE)
+
+#define PBOOT_LEFT_ICON_WIDTH		64
+#define PBOOT_LEFT_ICON_HEIGHT		64
+#define PBOOT_LEFT_ICON_XOFF		50
+#define PBOOT_LEFT_ICON_YOFF		50
+#define PBOOT_LEFT_ICON_STRIDE		100
+
+#define PBOOT_RIGHT_OPTION_LMARGIN	30
+#define PBOOT_RIGHT_OPTION_RMARGIN	30
+#define PBOOT_RIGHT_OPTION_TMARGIN	70
+#define PBOOT_RIGHT_OPTION_HEIGHT	64
+#define PBOOT_RIGHT_OPTION_STRIDE	100
+#define PBOOT_RIGHT_TITLE_TEXT_SIZE	(30 * TWIN_FIXED_ONE)
+#define PBOOT_RIGHT_SUBTITLE_TEXT_SIZE	(18 * TWIN_FIXED_ONE)
+#define PBOOT_RIGHT_TITLE_XOFFSET	80
+#define PBOOT_RIGHT_TITLE_YOFFSET	30
+#define PBOOT_RIGHT_SUBTITLE_XOFFSET	100
+#define PBOOT_RIGHT_SUBTITLE_YOFFSET	50
+#define PBOOT_RIGHT_BADGE_XOFFSET	2
+#define PBOOT_RIGHT_BADGE_YOFFSET	0
+
+
+#define PBOOT_RIGHT_TITLE_COLOR		0xff000000
+#define PBOOT_RIGHT_SUBTITLE_COLOR	0xff400000
+
+#define PBOOT_FOCUS_COLOR		0x10404040
+
+#define PBOOT_STATUS_PANE_COLOR		0x60606060
+#define PBOOT_STATUS_PANE_HEIGHT	20
+#define PBOOT_STATUS_PANE_XYMARGIN	20
+#define PBOOT_STATUS_TEXT_MARGIN	10
+#define PBOOT_STATUS_TEXT_SIZE		(16 * TWIN_FIXED_ONE)
+#define PBOOT_STATUS_TEXT_COLOR		0xff000000
+
+#define PBOOT_MAX_OPTION 100
+#define PBOOT_MAX_DEV 10
+
+struct pbt_option
+{
+	char		*title;
+	char		*subtitle;
+	twin_pixmap_t	*badge;
+	twin_pixmap_t	*cache;
+	twin_rect_t	box;
+	void		*data;
+};
+
+struct pbt_device
+{
+	char			*id;
+	twin_pixmap_t		*badge;
+	twin_rect_t		box;
+	int			option_count;
+	struct pbt_option		options[PBOOT_MAX_OPTION];
+};
+
+enum pbt_sig {
+	pbt_scr_sig = 111,
+	pbt_menu_sig = 222,
+	pbt_pane_sig = 333,
+	pb_removed_sig = -555,
+};
+
+struct pbt_cursor {
+	twin_pixmap_t *pixmap;
+	int hx;
+	int hy;
+};
+
+struct pbt_scr {
+	enum pbt_sig sig;
+	struct pbt_cursor cursor;
+	twin_screen_t *tscreen;
+#if defined(HAVE_LIBTWIN_TWIN_X11_H)
+	twin_x11_t *x11;
+#endif
+	twin_fbdev_t *fbdev;
+};
+
+struct pbt_frame {
+	twin_label_t *title;
+	twin_label_t *help;
+	twin_label_t *status;
+};
+
+/**
+ * struct pbt_pane - A twin menu pane.
+ */
+
+struct pbt_pane {
+	enum pbt_sig sig;
+	twin_window_t *window;
+	twin_rect_t focus_box;
+	int focus_start;
+	int focus_target;
+	int focus_curindex;
+	int mouse_target;
+	int has_focus;
+};
+
+/**
+ * struct pbt_menu - A twin menu.
+ * @sig: Sanity check signature.
+ * @scr: The screen this menu is associated with.
+ * @dp: The device pane instance.
+ * @op: The option pane instance.
+ */
+
+struct pbt_menu {
+	enum pbt_sig sig;
+	struct pbt_scr *scr;
+	struct pbt_pane *dp;
+	struct pbt_pane *op;
+};
+
+//==============================================================================
+// helper
+//==============================================================================
+
+static struct pbt_scr *pbt_scr_from_tscreen(twin_screen_t *tscreen);
+
+static struct pbt_menu *pbt_menu_from_twindow(twin_window_t *twindow)
+{
+	struct pbt_menu *menu = twindow->client_data;
+
+	assert(menu);
+	assert(menu->sig == pbt_menu_sig);
+	return menu;
+}
+
+/*
+static struct pbt_menu *pbt_menu_from_arg(void *arg)
+{
+	struct pbt_menu *menu = arg;
+
+	assert(menu);
+	assert(menu->sig == pbt_menu_sig);
+	return menu;
+}
+*/
+
+static struct pbt_pane *pbt_pane_from_arg(void *arg)
+{
+	struct pbt_pane *pane = arg;
+
+	assert(pane);
+	assert(pane->sig == pbt_pane_sig);
+	return pane;
+}
+
+static twin_bool_t pbt_rect_intersect(twin_rect_t r1, twin_rect_t r2)
+{
+	// FIXME: move this to twin!!!
+	return !(r1.left > r2.right ||
+		 r1.right < r2.left ||
+		 r1.top > r2.bottom ||
+		 r1.bottom < r2.top);
+}
+
+static twin_pixmap_t * pbt_load_background(twin_screen_t *tscreen)
+{
+	static const char *bgd = PB_ARTWORK_PATH "/background.jpg";
+	twin_pixmap_t *rawpix;
+	twin_pixmap_t *scaledpix;
+
+	rawpix = twin_jpeg_to_pixmap(bgd, TWIN_ARGB32);
+
+	if (!rawpix) {
+		pb_log("%s: loading image %s failed\n", __func__, bgd);
+		return twin_make_pattern();
+	}
+
+	if (tscreen->height == rawpix->height &&
+		tscreen->width == rawpix->width)
+		return rawpix;
+
+	/* Scale as needed. */
+
+	twin_fixed_t sx, sy;
+	twin_operand_t srcop;
+
+	scaledpix = twin_pixmap_create(TWIN_ARGB32,
+				tscreen->width,
+				tscreen->height);
+	if (!scaledpix) {
+		pb_log("%s: scale %s failed\n", __func__, bgd);
+		twin_pixmap_destroy(rawpix);
+		return twin_make_pattern();
+	}
+	sx = twin_fixed_div(twin_int_to_fixed(rawpix->width),
+			twin_int_to_fixed(tscreen->width));
+	sy = twin_fixed_div(twin_int_to_fixed(rawpix->height),
+			twin_int_to_fixed(tscreen->height));
+
+	twin_matrix_scale(&rawpix->transform, sx, sy);
+	srcop.source_kind = TWIN_PIXMAP;
+	srcop.u.pixmap = rawpix;
+	twin_composite(scaledpix, 0, 0, &srcop, 0, 0,
+		NULL, 0, 0, TWIN_SOURCE,
+		tscreen->width, tscreen->height);
+
+	twin_pixmap_destroy(rawpix);
+	return scaledpix;
+}
+
+//==============================================================================
+// option
+//==============================================================================
+
+static void pbt_option_execute(struct pbt_menu *menu)
+{
+#if 0
+	pboot_device_t *dev = pboot_devices[pboot_dev_sel];
+	pboot_option_t *opt = &dev->options[menu->op->focus_curindex];
+
+	pb_log("Selected device %s\n", opt->title);
+	pboot_message("booting %s...", opt->title);
+
+	/* Give user feedback, make sure errors and panics will be seen */
+	pboot_exec_option(opt->data);
+#endif
+}
+
+//==============================================================================
+// device
+//==============================================================================
+
+//==============================================================================
+// scr
+//==============================================================================
+
+static twin_bool_t pbt_scr_event(twin_screen_t *tscreen, twin_event_t *event)
+{
+	struct pbt_scr *scr = pbt_scr_from_tscreen(tscreen);
+
+	switch(event->kind) {
+	case TwinEventEnter:
+	case TwinEventMotion:
+	case TwinEventLeave:
+	case TwinEventButtonDown:
+	case TwinEventButtonUp:
+		if (scr->cursor.pixmap)
+			twin_screen_set_cursor(tscreen, scr->cursor.pixmap,
+				scr->cursor.hx, scr->cursor.hy);
+		break;
+	case TwinEventJoyButton:
+		/* map joystick events into key events */
+		if (event->u.js.control >= sizeof(sixaxis_map))
+			break;
+
+		event->u.key.key = sixaxis_map[event->u.js.control];
+		if (event->u.js.value == 0) {
+			event->kind = TwinEventKeyUp;
+			break;
+		} else {
+			event->kind = TwinEventKeyDown;
+		}
+		/* fall through.. */
+	case TwinEventKeyDown:
+		switch(event->u.key.key) {
+		case KEY_0:
+			return TWIN_TRUE;
+		case KEY_BACKSPACE:
+		case KEY_DELETE:
+			return TWIN_FALSE;
+		}
+	case TwinEventKeyUp:
+		twin_screen_set_cursor(tscreen, NULL, 0, 0);
+		break;
+	default:
+		break;
+	}
+	return TWIN_FALSE;
+}
+
+//==============================================================================
+// pane
+//==============================================================================
+
+static int pbt_pane_has_focus(const struct pbt_pane *pane)
+{
+	return pane->has_focus;
+}
+
+static twin_time_t pbt_pane_timeout(twin_time_t now, void *closure)
+{
+	const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 1, 2, 2, 3 };
+	struct pbt_pane *pane = pbt_pane_from_arg(closure);
+	int dir = 1, dist, pos;
+
+	dist = abs(pane->focus_target - pane->focus_start);
+	dir = dist > 5 ? 5 : dist;
+	pos = pane->focus_target - (int)pane->focus_box.top;
+	if (pos == 0) {
+		return -1;
+	}
+	if (pos < 0) {
+		dir = -dir;
+		pos = -pos;
+	}
+	twin_window_damage(pane->window,
+			   pane->focus_box.left,
+			   pane->focus_box.top,
+			   pane->focus_box.right,
+			   pane->focus_box.bottom);
+
+	pane->focus_box.top += dir;
+	pane->focus_box.bottom += dir;
+
+	twin_window_damage(pane->window,
+			   pane->focus_box.left,
+			   pane->focus_box.top,
+			   pane->focus_box.right,
+			   pane->focus_box.bottom);
+
+	twin_window_queue_paint(pane->window);
+
+	return accel[(pos * 10) / dist];
+}
+
+//==============================================================================
+// menu
+//==============================================================================
+
+/**
+ * pbt_menu_set_focus - Set the menu's pane of focus.
+ * @menu: The menu to operate on.
+ * @pane: The pane that will have the focus.
+ */
+
+static void pbt_menu_set_focus(struct pbt_menu *menu, struct pbt_pane *pane)
+{
+	assert(!pane->has_focus);
+
+	if (pane == menu->dp) {
+		menu->dp->has_focus = 1;
+		menu->op->has_focus = 0;
+	} else if (pane == menu->op) {
+		menu->dp->has_focus = 0;
+		menu->op->has_focus = 1;
+//		pbt_menu_set_option_focus(menu, 0);
+	} else
+		assert(0 && "bad logic");
+}
+
+static void pbt_menu_pane_select(struct pbt_menu *menu, struct pbt_pane *pane)
+{
+	if(pbt_pane_has_focus(pane))
+		return;
+
+	twin_screen_set_active(menu->scr->tscreen, pane->window->pixmap);
+
+	twin_window_damage(menu->dp->window,
+			   menu->dp->focus_box.left,
+			   menu->dp->focus_box.top,
+			   menu->dp->focus_box.right,
+			   menu->dp->focus_box.bottom);
+	twin_window_damage(menu->op->window,
+			   menu->op->focus_box.left,
+			   menu->op->focus_box.top,
+			   menu->op->focus_box.right,
+			   menu->op->focus_box.bottom);
+
+	twin_window_queue_paint(menu->dp->window);
+	twin_window_queue_paint(menu->op->window);
+
+	pbt_menu_set_focus(menu, pane);
+}
+
+
+static struct pbt_pane *pbt_device_pane_create(struct pbt_menu *menu);
+static struct pbt_pane *pbt_option_pane_create(struct pbt_menu *menu);
+
+static struct pbt_menu *pbt_menu_create(void *ctx, struct pbt_scr *scr)
+{
+	struct pbt_menu *menu = talloc_zero(ctx, struct pbt_menu);
+
+	if (!menu)
+		return NULL;
+
+	assert(scr && scr->sig == pbt_scr_sig);
+
+	menu->sig = pbt_menu_sig;
+	menu->scr = scr;
+
+	menu->dp = pbt_device_pane_create(menu);
+
+	if (!menu->dp)
+		goto fail_dp;
+
+	menu->op = pbt_option_pane_create(menu);
+
+	if (!menu->op)
+		goto fail_op;
+
+	return menu;
+
+fail_op:
+	//clean dp
+fail_dp:
+	talloc_free(menu);
+	return NULL;
+}
+
+//==============================================================================
+// device_pane
+//==============================================================================
+
+static void pbt_device_pane_draw(twin_window_t *window)
+{
+	struct pbt_pane *dp = pbt_menu_from_twindow(window)->dp;
+	twin_pixmap_t	*px = window->pixmap;
+	twin_path_t	*path;
+	twin_fixed_t	x, y, w, h;
+	int		i;
+
+	/* Fill background */
+	twin_fill(px, PBOOT_LEFT_PANE_COLOR, TWIN_SOURCE, 0, 0, px->width,
+		px->height);
+
+	/* Create a path for use later */
+	path = twin_path_create();
+	assert(path);
+
+	/* Draw right line if needed */
+	if (px->clip.right > (PBOOT_LEFT_PANE_SIZE - 4)) {
+		x = twin_int_to_fixed(PBOOT_LEFT_PANE_SIZE - 4);
+		y = twin_int_to_fixed(px->height);
+		twin_path_rectangle(path, x, 0, 0x40000, y);
+		twin_paint_path(px, PBOOT_LEFT_LINE_COLOR, path);
+		twin_path_empty(path);
+	}
+
+	/* Draw focus box */
+	if (dp->focus_curindex >= 0 &&
+	    pbt_rect_intersect(dp->focus_box, px->clip)) {
+		x = twin_int_to_fixed(dp->focus_box.left + 2);
+		y = twin_int_to_fixed(dp->focus_box.top + 2);
+		w = twin_int_to_fixed(dp->focus_box.right -
+				      dp->focus_box.left - 4);
+		h = twin_int_to_fixed(dp->focus_box.bottom -
+				      dp->focus_box.top - 4);
+		twin_path_rounded_rectangle(path, x, y, w, h,
+					    PBOOT_LEFT_FOCUS_XRAD,
+					    PBOOT_LEFT_FOCUS_YRAD);
+		if (pbt_pane_has_focus(dp))
+			twin_paint_path(px, PBOOT_FOCUS_COLOR, path);
+		else
+			twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path,
+					  4 * TWIN_FIXED_ONE);
+	}
+
+#if 0
+	/* Draw icons */
+	for (i = 0; i < pboot_dev_count; i++) {
+		pboot_device_t	*dev = pboot_devices[i];
+		twin_operand_t	src;
+
+		if (!twin_rect_intersect(dev->box, px->clip))
+			continue;
+
+		src.source_kind = TWIN_PIXMAP;
+		src.u.pixmap = dev->badge;
+
+		twin_composite(px, dev->box.left, dev->box.top,
+			       &src, 0, 0, NULL, 0, 0, TWIN_OVER,
+			       dev->box.right - dev->box.left,
+			       dev->box.bottom - dev->box.top);
+	}
+#endif
+
+	/* Destroy path */
+	twin_path_destroy(path);
+}
+
+
+static void pbt_device_pane_set_focus(struct pbt_menu *menu, int index)
+{
+#if 0
+	if (index >= pboot_dev_count)
+		return;
+#endif
+
+	menu->dp->focus_start = menu->dp->focus_box.top;
+
+	if (index < 0)
+		menu->dp->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT;
+	else
+		menu->dp->focus_target = PBOOT_LEFT_FOCUS_YOFF +
+			PBOOT_LEFT_ICON_STRIDE * index;
+
+	menu->dp->focus_curindex = index;
+
+	twin_set_timeout(pbt_pane_timeout, 0, menu->dp);
+}
+
+static void pbt_device_pane_mousetrack(struct pbt_menu *menu, twin_coord_t x,
+	twin_coord_t y)
+{
+	int candidate = -1;
+	twin_coord_t icon_top;
+
+	if (x < PBOOT_LEFT_ICON_XOFF ||
+	    x > (PBOOT_LEFT_ICON_XOFF + PBOOT_LEFT_ICON_WIDTH))
+		goto miss;
+
+	if (y < PBOOT_LEFT_ICON_YOFF)
+		goto miss;
+
+	candidate = (y - PBOOT_LEFT_ICON_YOFF) / PBOOT_LEFT_ICON_STRIDE;
+
+#if 0
+	if (candidate >= pboot_dev_count) {
+		candidate = -1;
+		goto miss;
+	}
+#endif
+	if (candidate == menu->dp->mouse_target)
+		return;
+
+	icon_top = PBOOT_LEFT_ICON_YOFF + candidate * PBOOT_LEFT_ICON_STRIDE;
+
+	if (y > (icon_top + PBOOT_LEFT_ICON_HEIGHT)) {
+		candidate = -1;
+		goto miss;
+	}
+
+	/* The mouse hit an icon that wasn't the same
+	 * as the previous one, trigger a focus change.
+	 */
+
+	pbt_device_pane_set_focus(menu, candidate);
+
+ miss:
+	menu->dp->mouse_target = candidate;
+}
+
+static twin_bool_t pbt_device_pane_event(twin_window_t *window,
+	twin_event_t *event)
+{
+	struct pbt_menu *menu = pbt_menu_from_twindow(window);
+
+	/* filter out all mouse events */
+	switch(event->kind) {
+	case TwinEventEnter:
+	case TwinEventMotion:
+	case TwinEventLeave:
+		pbt_menu_pane_select(menu, menu->dp);
+		pbt_device_pane_mousetrack(menu, event->u.pointer.x,
+			event->u.pointer.y);
+		return TWIN_TRUE;
+	case TwinEventButtonDown:
+	case TwinEventButtonUp:
+		return TWIN_TRUE;
+	case TwinEventKeyDown:
+		switch(event->u.key.key) {
+		case KEY_UP:
+			if (menu->dp->focus_curindex > 0)
+				pbt_device_pane_set_focus(menu,
+					menu->dp->focus_curindex - 1);
+			return TWIN_TRUE;
+		case KEY_DOWN:
+			pbt_device_pane_set_focus(menu, menu->dp->focus_curindex + 1);
+			return TWIN_TRUE;
+		case KEY_RIGHT:
+			pbt_menu_pane_select(menu, menu->op);
+			return TWIN_TRUE;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+	return TWIN_FALSE;
+}
+
+static struct pbt_pane *pbt_device_pane_create(struct pbt_menu *menu)
+{
+	struct pbt_pane *pane = talloc_zero(menu, struct pbt_pane);
+
+	if (!pane)
+		return NULL;
+
+	pane->sig = pbt_pane_sig;
+	pane->window = twin_window_create(menu->scr->tscreen, TWIN_ARGB32,
+		TwinWindowPlain, 0, 0, PBOOT_LEFT_PANE_SIZE,
+		menu->scr->tscreen->height);
+
+	if (!pane->window)
+		goto fail_window;
+
+	pane->window->draw = pbt_device_pane_draw;
+	pane->window->event = pbt_device_pane_event;
+	pane->window->client_data = menu;
+
+	pane->focus_curindex = -1;
+	pane->focus_box.left = PBOOT_LEFT_FOCUS_XOFF;
+	pane->focus_box.top = -2 * PBOOT_LEFT_FOCUS_HEIGHT;
+	pane->focus_box.right = pane->focus_box.left + PBOOT_LEFT_FOCUS_WIDTH;
+	pane->focus_box.bottom = pane->focus_box.top + PBOOT_LEFT_FOCUS_HEIGHT;
+
+	pane->mouse_target = -1;
+
+	twin_window_show(pane->window);
+	twin_window_queue_paint(pane->window);
+
+	return pane;
+
+fail_window:
+	talloc_free(pane);
+	return NULL;
+}
+
+//==============================================================================
+// option_pane
+//==============================================================================
+
+static void pbt_option_pane_set_focus(struct pbt_menu *menu, int index)
+{
+#if 0
+	pboot_device_t	*dev;
+
+	if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count)
+		return;
+	dev = pboot_devices[pboot_dev_sel];
+	if (index < 0 || index >= dev->option_count)
+		return;
+#endif
+
+	menu->op->focus_start = menu->op->focus_box.top;
+	menu->op->focus_target = PBOOT_RIGHT_FOCUS_YOFF +
+		PBOOT_RIGHT_OPTION_STRIDE * index;
+	menu->op->focus_curindex = index;
+
+	twin_set_timeout(pbt_pane_timeout, 0, menu->op);
+}
+
+static void pbt_option_pane_draw(twin_window_t *window)
+{
+	struct pbt_pane *op = pbt_menu_from_twindow(window)->op;
+	twin_pixmap_t	*px = window->pixmap;
+//	pboot_device_t	*dev;
+	twin_path_t	*path;
+	twin_fixed_t	x, y, w, h;
+	int		i;
+
+	/* Fill background */
+	twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height);
+
+	/* Nothing to draw, return */
+//	if (pboot_dev_sel < 0)
+//		return;
+
+	/* Create a path for use later */
+	path = twin_path_create();
+	assert(path);
+
+	/* Draw focus box */
+	if (op->focus_curindex >= 0 &&
+	    pbt_rect_intersect(op->focus_box, px->clip)) {
+		x = twin_int_to_fixed(op->focus_box.left + 2);
+		y = twin_int_to_fixed(op->focus_box.top + 2);
+		w = twin_int_to_fixed(op->focus_box.right -
+				      op->focus_box.left - 4);
+		h = twin_int_to_fixed(op->focus_box.bottom -
+				      op->focus_box.top - 4);
+		twin_path_rounded_rectangle(path, x, y, w, h,
+					    PBOOT_RIGHT_FOCUS_XRAD,
+					    PBOOT_RIGHT_FOCUS_YRAD);
+		if (pbt_pane_has_focus(op))
+			twin_paint_path(px, PBOOT_FOCUS_COLOR, path);
+		else
+			twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path,
+					  4 * TWIN_FIXED_ONE);
+	}
+
+	/* Get device and iterate through options */
+/*
+	dev = pboot_devices[pboot_dev_sel];
+	for (i = 0; i < dev->option_count; i++) {
+		pboot_option_t	*opt = &dev->options[i];
+		twin_operand_t	src;
+
+		if (opt->title == NULL)
+			continue;
+		if (!pbt_rect_intersect(opt->box, px->clip))
+			continue;
+		if (opt->cache == NULL)
+			pboot_draw_option_cache(dev, opt, i);
+
+		src.source_kind = TWIN_PIXMAP;
+		src.u.pixmap = opt->cache;
+
+		twin_composite(px, opt->box.left, opt->box.top,
+			       &src, 0, 0, NULL, 0, 0, TWIN_OVER,
+			       opt->box.right - opt->box.left,
+			       opt->box.bottom - opt->box.top);
+	}
+*/
+	/* Destroy path */
+	twin_path_destroy(path);
+}
+
+static void pbt_option_pane_mousetrack(struct pbt_menu *menu, twin_coord_t x,
+	twin_coord_t y)
+{
+	int candidate = -1;
+
+	if (y < PBOOT_RIGHT_OPTION_TMARGIN)
+		goto miss;
+
+#if 0
+	pboot_device_t	*dev;
+	pboot_option_t	*opt;
+
+	if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count)
+		return;
+
+	dev = pboot_devices[pboot_dev_sel];
+
+	candidate = (y - PBOOT_RIGHT_OPTION_TMARGIN) /
+		PBOOT_RIGHT_OPTION_STRIDE;
+
+	if (candidate >= dev->option_count) {
+		candidate = -1;
+		goto miss;
+	}
+
+	if (candidate == op->mouse_target)
+		return;
+
+	opt = &dev->options[candidate];
+
+	if (x < opt->box.left || x > opt->box.right ||
+	    y < opt->box.top || y > opt->box.bottom) {
+		candidate = -1;
+		goto miss;
+	}
+#endif
+
+	pbt_option_pane_set_focus(menu, candidate);
+
+miss:
+	menu->op->mouse_target = candidate;
+}
+
+static twin_bool_t pbt_option_pane_event(twin_window_t *window,
+	twin_event_t *event)
+{
+	struct pbt_menu *menu = pbt_menu_from_twindow(window);
+
+	/* filter out all mouse events */
+	switch(event->kind) {
+	case TwinEventEnter:
+	case TwinEventMotion:
+	case TwinEventLeave:
+		pbt_menu_pane_select(menu, menu->op);
+		pbt_option_pane_mousetrack(menu, event->u.pointer.x,
+			event->u.pointer.y);
+		return TWIN_TRUE;
+	case TwinEventButtonDown:
+		pbt_menu_pane_select(menu, menu->op);
+		pbt_option_pane_mousetrack(menu, event->u.pointer.x,
+			event->u.pointer.y);
+		pbt_option_execute(menu);
+		return TWIN_TRUE;
+	case TwinEventButtonUp:
+		return TWIN_TRUE;
+	case TwinEventKeyDown:
+		switch(event->u.key.key) {
+		case KEY_UP:
+			//pbt_menu_set_option_focus(menu, menu->op->focus_curindex - 1);
+			return TWIN_TRUE;
+		case KEY_DOWN:
+			//pbt_menu_set_option_focus(menu, menu->op->focus_curindex + 1);
+			return TWIN_TRUE;
+		case KEY_LEFT:
+			pbt_menu_pane_select(menu, menu->dp);
+			return TWIN_TRUE;
+		case KEY_ENTER:
+			pbt_option_execute(menu);
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+	return TWIN_FALSE;
+}
+
+static struct pbt_pane *pbt_option_pane_create(struct pbt_menu *menu)
+{
+	struct pbt_pane *pane = talloc_zero(menu, struct pbt_pane);
+
+	if (!pane)
+		return NULL;
+
+	pane->sig = pbt_pane_sig;
+	pane->window = twin_window_create(menu->scr->tscreen, TWIN_ARGB32,
+		TwinWindowPlain, PBOOT_LEFT_PANE_SIZE, 0,
+		menu->scr->tscreen->width - PBOOT_LEFT_PANE_SIZE,
+		menu->scr->tscreen->height);
+
+	if (!pane->window)
+		goto fail_window;
+
+	pane->window->draw = pbt_option_pane_draw;
+	pane->window->event = pbt_option_pane_event;
+	pane->window->client_data = menu;
+
+	pane->focus_curindex = -1;
+	pane->focus_box.left = PBOOT_RIGHT_FOCUS_XOFF;
+	pane->focus_box.top = -2 * PBOOT_RIGHT_FOCUS_HEIGHT;
+	pane->focus_box.right = pane->window->pixmap->width
+		- 2 * PBOOT_RIGHT_FOCUS_XOFF;
+	pane->focus_box.bottom = pane->focus_box.top + PBOOT_RIGHT_FOCUS_HEIGHT;
+
+	pane->mouse_target = -1;
+
+	twin_window_show(pane->window);
+	twin_window_queue_paint(pane->window);
+
+	return pane;
+
+fail_window:
+	talloc_free(pane);
+	return NULL;
+}
+
+//==============================================================================
+// junk
+//==============================================================================
+
+#if 0
+
+static pboot_device_t	*pboot_devices[PBOOT_MAX_DEV];
+static int		pboot_dev_count;
+static int		pboot_dev_sel = -1;
+
+
+
+
+static int pboot_vmode_change = -1;
+
+static void pboot_message(const char *fmt, ...)
+{
+	va_list ap;
+	char *msg;
+
+	va_start(ap, fmt);
+	vasprintf(&msg, fmt, ap);
+	va_end(ap);
+
+	pb_log(msg);
+}
+
+static void pboot_draw_option_cache(pboot_device_t *dev, pboot_option_t *opt,
+				    int index)
+{
+	twin_pixmap_t	*px;
+	twin_path_t	*path;
+	twin_fixed_t	tx, ty;
+
+	/* Create pixmap */
+	px = twin_pixmap_create(TWIN_ARGB32, opt->box.right - opt->box.left,
+				 opt->box.bottom - opt->box.top);
+	assert(px);
+	opt->cache = px;
+
+	/* Fill background */
+	twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height);
+
+	/* Allocate a path for drawing */
+	path = twin_path_create();
+	assert(path);
+
+#if 0
+	/* TEST - Bounding rectangle */
+	twin_path_rectangle(path, 0, 0,
+			    twin_int_to_fixed(px->width),
+			    twin_int_to_fixed(px->height));
+	twin_paint_path(px, PBOOT_RIGHT_TITLE_COLOR, path);
+	twin_path_empty(path);
+	twin_fill(px, 0x00000000, TWIN_SOURCE, 2, 2,
+		  px->width - 3, px->height - 3);
+#endif
+
+	/* Draw texts */
+	twin_path_set_font_size(path, PBOOT_RIGHT_TITLE_TEXT_SIZE);
+	twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
+	tx = twin_int_to_fixed(PBOOT_RIGHT_TITLE_XOFFSET);
+	ty = twin_int_to_fixed(PBOOT_RIGHT_TITLE_YOFFSET);
+	twin_path_move (path, tx, ty);
+	twin_path_utf8 (path, opt->title);
+	twin_paint_path (px, PBOOT_RIGHT_TITLE_COLOR, path);
+	twin_path_empty (path);
+
+	if (opt->subtitle) {
+		twin_path_set_font_size(path, PBOOT_RIGHT_SUBTITLE_TEXT_SIZE);
+		twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
+		tx = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_XOFFSET);
+		ty = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_YOFFSET);
+		twin_path_move (path, tx, ty);
+		twin_path_utf8 (path, opt->subtitle);
+		twin_paint_path (px, PBOOT_RIGHT_SUBTITLE_COLOR, path);
+		twin_path_empty (path);
+	}
+
+	if (opt->badge) {
+		twin_operand_t	src;
+
+		src.source_kind = TWIN_PIXMAP;
+		src.u.pixmap = opt->badge;
+
+		twin_composite(px, PBOOT_RIGHT_BADGE_XOFFSET,
+			       PBOOT_RIGHT_BADGE_YOFFSET,
+			       &src, 0, 0, NULL, 0, 0, TWIN_OVER,
+			       opt->badge->width, opt->badge->height);
+	}
+
+
+	/* Destroy path */
+	twin_path_destroy(path);
+}
+
+
+
+static int pboot_add_option(int devindex, const char *title,
+		     const char *subtitle, twin_pixmap_t *badge, void *data)
+{
+	pboot_device_t	*dev;
+	pboot_option_t	*opt;
+	twin_coord_t	width;
+	int		index;
+	struct pbt_menu *menu = NULL;
+
+	if (devindex < 0 || devindex >= pboot_dev_count)
+		return -1;
+	dev = pboot_devices[devindex];
+
+	if (dev->option_count >= PBOOT_MAX_OPTION)
+		return -1;
+	index = dev->option_count++;
+	opt = &dev->options[index];
+
+	opt->title = malloc(strlen(title) + 1);
+	strcpy(opt->title, title);
+
+	if (subtitle) {
+		opt->subtitle = malloc(strlen(subtitle) + 1);
+		strcpy(opt->subtitle, subtitle);
+	} else
+		opt->subtitle = NULL;
+
+	opt->badge = badge;
+	opt->cache = NULL;
+
+	width = menu->op->window->pixmap->width -
+		(PBOOT_RIGHT_OPTION_LMARGIN + PBOOT_RIGHT_OPTION_RMARGIN);
+
+	opt->box.left = PBOOT_RIGHT_OPTION_LMARGIN;
+	opt->box.right = opt->box.left + width;
+	opt->box.top = PBOOT_RIGHT_OPTION_TMARGIN +
+		index * PBOOT_RIGHT_OPTION_STRIDE;
+	opt->box.bottom = opt->box.top + PBOOT_RIGHT_OPTION_HEIGHT;
+
+	opt->data = data;
+	return index;
+}
+
+static void pboot_set_device_select(struct pbt_menu *menu, int sel, int force)
+{
+	pb_log("%s: %d -> %d\n", __FUNCTION__, pboot_dev_sel, sel);
+	if (!force && sel == pboot_dev_sel)
+		return;
+	if (sel >= pboot_dev_count)
+		return;
+	pboot_dev_sel = sel;
+	if (force) {
+		menu->dp->focus_curindex = sel;
+		if (sel < 0)
+			menu->dp->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT;
+		else
+			menu->dp->focus_target = PBOOT_LEFT_FOCUS_YOFF +
+				PBOOT_LEFT_ICON_STRIDE * sel;
+		menu->op->focus_box.bottom = menu->dp->focus_target;
+		menu->op->focus_box.bottom = menu->op->focus_box.top +
+			PBOOT_RIGHT_FOCUS_HEIGHT;
+		twin_window_damage(menu->dp->window,
+				   0, 0,
+				   menu->dp->window->pixmap->width,
+				   menu->dp->window->pixmap->height);
+		twin_window_queue_paint(menu->dp->window);
+	}
+	menu->op->focus_curindex = -1;
+	menu->op->mouse_target = -1;
+	menu->op->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT;
+	menu->op->focus_box.bottom = menu->op->focus_box.top +
+		PBOOT_RIGHT_FOCUS_HEIGHT;
+	twin_window_damage(menu->op->window, 0, 0,
+			   menu->op->window->pixmap->width,
+			   menu->op->window->pixmap->height);
+	twin_window_queue_paint(menu->op->window);
+}
+
+static void pboot_quit(void)
+{
+	kill(0, SIGINT);
+}
+
+
+static int pboot_add_device(const char *dev_id, twin_pixmap_t *pixmap)
+{
+	int		index;
+	pboot_device_t	*dev;
+
+	struct pbt_menu *menu = NULL;
+
+	if (pboot_dev_count >= PBOOT_MAX_DEV)
+		return -1;
+
+	index = pboot_dev_count++;
+
+	dev = malloc(sizeof(*dev));
+	memset(dev, 0, sizeof(*dev));
+	dev->id = malloc(strlen(dev_id) + 1);
+	strcpy(dev->id, dev_id);
+	dev->badge = pixmap;
+	dev->box.left = PBOOT_LEFT_ICON_XOFF;
+	dev->box.right = dev->box.left + PBOOT_LEFT_ICON_WIDTH;
+	dev->box.top = PBOOT_LEFT_ICON_YOFF +
+		PBOOT_LEFT_ICON_STRIDE * index;
+	dev->box.bottom = dev->box.top + PBOOT_LEFT_ICON_HEIGHT;
+
+	pboot_devices[index] = dev;
+
+	twin_window_damage(menu->dp->window,
+			   dev->box.left, dev->box.top,
+			   dev->box.right, dev->box.bottom);
+	twin_window_queue_paint(menu->dp->window);
+
+	return index;
+}
+
+static int pboot_remove_device(const char *dev_id)
+{
+	pboot_device_t	*dev = NULL;
+	int		i, newsel = pboot_dev_sel;
+
+	struct pbt_menu *menu = NULL;
+
+	/* find the matching device */
+	for (i = 0; i < pboot_dev_count; i++) {
+		if (!strcmp(pboot_devices[i]->id, dev_id)) {
+			dev = pboot_devices[i];
+			break;
+		}
+	}
+
+	if (!dev)
+		return TWIN_FALSE;
+
+	memmove(pboot_devices + i, pboot_devices + i + 1,
+			sizeof(*pboot_devices) * (pboot_dev_count + i - 1));
+	pboot_devices[--pboot_dev_count] = NULL;
+
+	/* select the newly-focussed device */
+	if (pboot_dev_sel > i)
+		newsel = pboot_dev_sel - 1;
+	else if (pboot_dev_sel == i && i >= pboot_dev_count)
+			newsel = pboot_dev_count - 1;
+	pboot_set_device_select(menu, newsel, 1);
+
+	/* todo: free device & options */
+
+	return TWIN_TRUE;
+}
+#endif
+
+//------------------------------------------------------------------------------
+
+static void print_version(void)
+{
+	printf("pb-twin (" PACKAGE_NAME ") " PACKAGE_VERSION "\n");
+}
+
+static void print_usage(void)
+{
+	print_version();
+	printf(
+"Usage: pb-twin [-h, --help] [-l, --log log-file] [-r, --reset-defaults]\n"
+"               [-t, --timeout] [-V, --version]\n");
+}
+
+/**
+ * enum opt_value - Tri-state options variables.
+ */
+
+enum opt_value {opt_undef = 0, opt_yes, opt_no};
+
+/**
+ * struct opts - Values from command line options.
+ */
+
+struct opts {
+	enum opt_value show_help;
+	const char *log_file;
+	enum opt_value reset_defaults;
+	enum opt_value use_timeout;
+	enum opt_value show_version;
+};
+
+/**
+ * opts_parse - Parse the command line options.
+ */
+
+static int opts_parse(struct opts *opts, int argc, char *argv[])
+{
+	static const struct option long_options[] = {
+		{"help",           no_argument,       NULL, 'h'},
+		{"log",            required_argument, NULL, 'l'},
+		{"reset-defaults", no_argument,       NULL, 'r'},
+		{"timeout",        no_argument,       NULL, 't'},
+		{"version",        no_argument,       NULL, 'V'},
+		{ NULL, 0, NULL, 0},
+	};
+	static const char short_options[] = "hl:trV";
+	static const struct opts default_values = {
+		.log_file = "pb-twin.log",
+	};
+
+	*opts = default_values;
+
+	while (1) {
+		int c = getopt_long(argc, argv, short_options, long_options,
+			NULL);
+
+		if (c == EOF)
+			break;
+
+		switch (c) {
+		case 'h':
+			opts->show_help = opt_yes;
+			break;
+		case 'l':
+			opts->log_file = optarg;
+			break;
+		case 't':
+			opts->use_timeout = opt_yes;
+			break;
+		case 'r':
+			opts->reset_defaults = opt_yes;
+			break;
+		case 'V':
+			opts->show_version = opt_yes;
+			break;
+		default:
+			opts->show_help = opt_yes;
+			return -1;
+		}
+	}
+
+	return optind != argc;
+}
+
+/**
+ * struct ps3_gui - Main gui program instance.
+ */
+
+
+struct ps3_gui {
+	struct ui_timer timer;
+	struct ps3_flash_values values;
+	int dirty_values;
+
+	struct pbt_scr scr;
+	struct pbt_frame frame;
+	struct pbt_menu *menu;
+};
+
+static struct ps3_gui ps3;
+
+static struct pbt_scr *pbt_scr_from_tscreen(twin_screen_t *tscreen)
+{
+	assert(ps3.scr.sig == pbt_scr_sig);
+	assert(ps3.scr.tscreen == tscreen);
+	return &ps3.scr;
+}
+
+static void sig_handler(int signum)
+{
+	DBGS("%d\n", signum);
+
+	switch (signum) {
+	case SIGALRM:
+		ui_timer_sigalrm(&ps3.timer);
+		break;
+	case SIGWINCH:
+//		if (ps3.gui)
+//			gui_resize(ps3.gui);
+		break;
+	default:
+		assert(0 && "unknown sig");
+		/* fall through */
+	case SIGINT:
+	case SIGHUP:
+	case SIGTERM:
+		exit(EXIT_FAILURE);
+//		if (ps3.gui)
+//			gui_abort(ps3.gui);
+		break;
+	}
+}
+
+/**
+ * main - twin bootloader main routine.
+ */
+
+int main(int argc, char *argv[])
+{
+	static struct sigaction sa;
+	static struct opts opts;
+	int result;
+	int ui_result;
+	unsigned int mode;
+	FILE *log;
+
+	result = opts_parse(&opts, argc, argv);
+
+	if (result) {
+		print_usage();
+		return EXIT_FAILURE;
+	}
+
+	if (opts.show_help == opt_yes) {
+		print_usage();
+		return EXIT_SUCCESS;
+	}
+
+	if (opts.show_version == opt_yes) {
+		print_version();
+		return EXIT_SUCCESS;
+	}
+
+	log = fopen(opts.log_file, "a");
+	assert(log);
+	pb_log_set_stream(log);
+
+#if defined(DEBUG)
+	pb_log_always_flush(1);
+#endif
+
+	pb_log("--- pb-twin ---\n");
+
+	sa.sa_handler = sig_handler;
+	result = sigaction(SIGALRM, &sa, NULL);
+	result += sigaction(SIGHUP, &sa, NULL);
+	result += sigaction(SIGINT, &sa, NULL);
+	result += sigaction(SIGTERM, &sa, NULL);
+	result += sigaction(SIGWINCH, &sa, NULL);
+
+	if (result) {
+		pb_log("%s sigaction failed.\n", __func__);
+		return EXIT_FAILURE;
+	}
+
+	ps3.values = ps3_flash_defaults;
+
+	if (opts.reset_defaults != opt_yes)
+		ps3.dirty_values = ps3_flash_get_values(&ps3.values);
+
+	twin_feature_init(); // need it???
+
+	/* Setup screen. */
+
+	ps3.scr.sig = pbt_scr_sig;
+
+#if defined(HAVE_LIBTWIN_TWIN_X11_H)
+# if defined(USE_X11)
+	if (1) {
+# else
+	if (0) {
+# endif
+		ps3.scr.x11 = twin_x11_create(XOpenDisplay(0), 1024, 768);
+
+		if (!ps3.scr.x11) {
+			perror("failed to create x11 screen !\n");
+			return EXIT_FAILURE;
+		}
+
+		ps3.scr.tscreen = ps3.scr.x11->screen;
+	} else {
+#else
+	if (1) {
+#endif
+		result = ps3_get_video_mode(&mode);
+
+		/* Current becomes default if ps3_flash_get_values() failed. */
+
+		if (ps3.dirty_values && !result)
+			ps3.values.video_mode = mode;
+
+		/* Set mode if not at default. */
+
+		if (!result && (ps3.values.video_mode != (uint16_t)mode))
+			ps3_set_video_mode(ps3.values.video_mode);
+
+		ps3.scr.fbdev = twin_fbdev_create(-1, SIGUSR1);
+
+		if (!ps3.scr.fbdev) {
+			perror("failed to create fbdev screen !\n");
+			return EXIT_FAILURE;
+		}
+
+		ps3.scr.tscreen = ps3.scr.fbdev->screen;
+	}
+
+	ps3.scr.tscreen->event_filter = pbt_scr_event;
+
+	twin_screen_set_background(ps3.scr.tscreen,
+		pbt_load_background(ps3.scr.tscreen));
+
+	/* setup menu */
+
+	ps3.menu = pbt_menu_create(NULL, &ps3.scr);
+
+	pbt_device_pane_set_focus(ps3.menu, 0);
+	twin_screen_set_active(ps3.scr.tscreen, ps3.menu->dp->window->pixmap);
+
+	/* Console switch */
+
+	if (ps3.scr.fbdev)
+		twin_fbdev_activate(ps3.scr.fbdev);
+
+	/* run twin */
+
+//	twin_toplevel_show(ps3.toplevel);
+	twin_dispatch();
+
+	pb_log("--- end ---\n");
+
+	return ui_result ? EXIT_FAILURE : EXIT_SUCCESS;
+}
