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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_LIBTWIN_TWIN_X11_H) +# include +#endif +#include + +#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; +}