mirror of
https://xff.cz/git/u-boot/
synced 2025-08-31 16:22:36 +02:00
cmd: tmenu: Add tmenu command for touch menu user interface
Shows a menu of items that can be controlled using touch panel. This is useful for touch based devices like tablets to select alternative boot options (multi-boot). Signed-off-by: Ondrej Jirman <megous@megous.com>
This commit is contained in:
committed by
Ondrej Jirman
parent
c062af533e
commit
e36a857b65
@@ -35,7 +35,7 @@ static int extlinux_get_state_desc(struct udevice *dev, char *buf, int maxsize)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extlinux_getfile(struct pxe_context *ctx, const char *file_path,
|
||||
int extlinux_getfile(struct pxe_context *ctx, const char *file_path,
|
||||
char *file_addr, ulong *sizep)
|
||||
{
|
||||
struct extlinux_info *info = ctx->userdata;
|
||||
|
@@ -783,6 +783,11 @@ cleanup:
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pxe_label_boot(struct pxe_context *ctx, struct pxe_label *label)
|
||||
{
|
||||
return label_boot(ctx, label);
|
||||
}
|
||||
|
||||
/** enum token_type - Tokens for the pxe file parser */
|
||||
enum token_type {
|
||||
T_EOL,
|
||||
|
@@ -2301,6 +2301,14 @@ config CMD_TIMER
|
||||
help
|
||||
Access the system timer.
|
||||
|
||||
config CMD_TMENU
|
||||
bool "tmenu - Display boot menu and respond to touch"
|
||||
depends on DM_TOUCHPANEL && VIDEO
|
||||
help
|
||||
Shows a menu of items that can be controlled using touch panel.
|
||||
This is useful for touch based devices like tablets to select
|
||||
alternative boot options (multi-boot).
|
||||
|
||||
config CMD_SOUND
|
||||
bool "sound"
|
||||
depends on SOUND
|
||||
|
@@ -177,6 +177,7 @@ obj-$(CONFIG_CMD_TERMINAL) += terminal.o
|
||||
obj-$(CONFIG_CMD_TIME) += time.o
|
||||
obj-$(CONFIG_CMD_TIMER) += timer.o
|
||||
obj-$(CONFIG_CMD_TOUCH) += touch.o
|
||||
obj-$(CONFIG_CMD_TMENU) += tmenu.o
|
||||
obj-$(CONFIG_CMD_TRACE) += trace.o
|
||||
obj-$(CONFIG_HUSH_PARSER) += test.o
|
||||
obj-$(CONFIG_CMD_TPM) += tpm-common.o
|
||||
|
759
cmd/tmenu.c
Normal file
759
cmd/tmenu.c
Normal file
@@ -0,0 +1,759 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2018-2024 Ondrej Jirman <megous@megous.com>
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <bootflow.h>
|
||||
#include <bootstd.h>
|
||||
#include <command.h>
|
||||
#include <cli_hush.h>
|
||||
#include <video.h>
|
||||
#include <video_font.h>
|
||||
#include <dm/uclass.h>
|
||||
#include <dm/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <video_font_8x16.h>
|
||||
#include <touchpanel.h>
|
||||
#include <led.h>
|
||||
#include <button.h>
|
||||
#include <sysreset.h>
|
||||
#include <cli.h>
|
||||
|
||||
#define VIDEO_FONT_HEIGHT 16
|
||||
#define VIDEO_FONT_WIDTH 8
|
||||
#define video_fontdata video_fontdata_8x16
|
||||
|
||||
// first some generic drawing primitives
|
||||
|
||||
struct painter {
|
||||
u8* fb;
|
||||
u8* fb_end;
|
||||
u8* cur;
|
||||
u32 line_length;
|
||||
u32 bpp;
|
||||
u32 rows;
|
||||
u32 cols;
|
||||
};
|
||||
|
||||
static void painter_set_xy(struct painter* p, uint x, uint y)
|
||||
{
|
||||
p->cur = p->fb + min(y, p->rows - 1) * p->line_length + min(x, p->cols - 1) * p->bpp;
|
||||
}
|
||||
|
||||
static void painter_move_dxy(struct painter* p, int dx, int dy)
|
||||
{
|
||||
p->cur += dy * p->line_length + dx * p->bpp;
|
||||
|
||||
if (p->cur >= p->fb_end)
|
||||
p->cur = p->fb_end - 1;
|
||||
|
||||
if (p->cur < p->fb)
|
||||
p->cur = p->fb;
|
||||
}
|
||||
|
||||
static void painter_rect_fill(struct painter* p, uint w, uint h, u32 color)
|
||||
{
|
||||
int x, y;
|
||||
u32* cur;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
cur = (u32*)(p->cur + p->line_length * y);
|
||||
|
||||
for (x = 0; x < w; x++)
|
||||
*(cur++) = color;
|
||||
}
|
||||
}
|
||||
|
||||
__maybe_unused
|
||||
static void painter_line_h(struct painter* p, int dx, u32 color)
|
||||
{
|
||||
if (dx < 0) {
|
||||
painter_move_dxy(p, 0, dx);
|
||||
painter_rect_fill(p, 1, -dx, color);
|
||||
} else {
|
||||
painter_rect_fill(p, 1, dx, color);
|
||||
painter_move_dxy(p, 0, dx);
|
||||
}
|
||||
}
|
||||
|
||||
__maybe_unused
|
||||
static void painter_line_v(struct painter* p, int dy, u32 color)
|
||||
{
|
||||
if (dy < 0) {
|
||||
painter_move_dxy(p, 0, dy);
|
||||
painter_rect_fill(p, 1, -dy, color);
|
||||
} else {
|
||||
painter_rect_fill(p, 1, dy, color);
|
||||
painter_move_dxy(p, 0, dy);
|
||||
}
|
||||
}
|
||||
|
||||
static void painter_bigchar(struct painter* p, char ch, u32 color)
|
||||
{
|
||||
int i, row;
|
||||
void *line = p->cur;
|
||||
|
||||
for (row = 0; row < VIDEO_FONT_HEIGHT * 2; row++) {
|
||||
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row / 2];
|
||||
uint32_t *dst = line;
|
||||
|
||||
for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
|
||||
if (bits & 0x80) {
|
||||
*dst = color;
|
||||
*(dst+1) = color;
|
||||
}
|
||||
|
||||
bits <<= 1;
|
||||
dst+=2;
|
||||
}
|
||||
|
||||
line += p->line_length;
|
||||
}
|
||||
|
||||
painter_move_dxy(p, VIDEO_FONT_WIDTH * 2, 0);
|
||||
}
|
||||
|
||||
__maybe_unused
|
||||
static void painter_char(struct painter* p, char ch, u32 color)
|
||||
{
|
||||
int i, row;
|
||||
void *line = p->cur;
|
||||
|
||||
for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
|
||||
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
|
||||
uint32_t *dst = line;
|
||||
|
||||
for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
|
||||
if (bits & 0x80)
|
||||
*dst = color;
|
||||
|
||||
bits <<= 1;
|
||||
dst++;
|
||||
}
|
||||
|
||||
line += p->line_length;
|
||||
}
|
||||
|
||||
painter_move_dxy(p, VIDEO_FONT_WIDTH, 0);
|
||||
}
|
||||
|
||||
// menu command
|
||||
|
||||
struct ui_item {
|
||||
int x, y, w, h;
|
||||
char text[40];
|
||||
uint32_t fill;
|
||||
uint32_t text_color;
|
||||
int id;
|
||||
};
|
||||
|
||||
static void ui_draw(struct ui_item *items, int n_items, struct painter *p)
|
||||
{
|
||||
for (int idx = 0; idx < n_items; idx++) {
|
||||
struct ui_item* i = items + idx;
|
||||
|
||||
painter_set_xy(p, i->x, i->y);
|
||||
painter_rect_fill(p, i->w, i->h, i->fill);
|
||||
|
||||
size_t max_chars = i->w / (VIDEO_FONT_WIDTH * 2) - 1;
|
||||
|
||||
max_chars = min(max_chars, strlen(i->text));
|
||||
|
||||
int text_w = i->w - max_chars * VIDEO_FONT_WIDTH * 2;
|
||||
int text_h = i->h - VIDEO_FONT_HEIGHT * 2;
|
||||
|
||||
painter_set_xy(p,
|
||||
i->x + text_w / 2,
|
||||
i->y + text_h / 2);
|
||||
|
||||
for (int j = 0; j < max_chars; j++)
|
||||
painter_bigchar(p, i->text[j], i->text_color);
|
||||
}
|
||||
}
|
||||
|
||||
static struct ui_item* ui_hit_find(struct ui_item* items, int n_items, int x, int y)
|
||||
{
|
||||
for (int idx = 0; idx < n_items; idx++) {
|
||||
struct ui_item* i = items + idx;
|
||||
|
||||
if (x >= i->x && x <= i->x + i->w && y >= i->y && y <= i->y + i->h)
|
||||
return i;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int handle_tmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[], int no_touch)
|
||||
{
|
||||
struct udevice *vdev, *tdev;
|
||||
struct video_priv *vpriv;
|
||||
struct touchpanel_touch touches[10];
|
||||
int ret;
|
||||
|
||||
if (argc < 2)
|
||||
return CMD_RET_USAGE;
|
||||
|
||||
// set some params: (parse from argv in the future)
|
||||
char* const* items = argv + 1;
|
||||
int n_items = argc - 1;
|
||||
|
||||
ret = uclass_first_device_err(UCLASS_VIDEO, &vdev);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
|
||||
if (!no_touch) {
|
||||
ret = uclass_first_device_err(UCLASS_TOUCHPANEL, &tdev);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
vpriv = dev_get_uclass_priv(vdev);
|
||||
|
||||
if (vpriv->bpix != VIDEO_BPP32) {
|
||||
printf("tmenu requires 32BPP video device\n");
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
struct painter p = {
|
||||
.fb = vpriv->fb,
|
||||
.fb_end = vpriv->fb + vpriv->fb_size,
|
||||
.cur = vpriv->fb,
|
||||
.line_length = vpriv->line_length,
|
||||
.bpp = VNBYTES(vpriv->bpix),
|
||||
.cols = vpriv->xsize,
|
||||
.rows = vpriv->ysize,
|
||||
};
|
||||
|
||||
// prepare ui_items and lay them out
|
||||
struct ui_item ui_items[n_items];
|
||||
int border = 40, max_total_h = min(800, vpriv->ysize - 2 * border);
|
||||
int gap = 10;
|
||||
|
||||
int cols = 1;
|
||||
while ((max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / DIV_ROUND_UP(n_items, cols) < 100)
|
||||
cols++;
|
||||
int rows = DIV_ROUND_UP(n_items, cols);
|
||||
|
||||
int item_h = (max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / rows;
|
||||
int item_w = (vpriv->xsize - 2 * border - (cols - 1) * gap) / cols;
|
||||
|
||||
int top = vpriv->ysize - border - rows * (item_h + gap) - gap;
|
||||
|
||||
for (int idx = 0; idx < n_items; idx++) {
|
||||
struct ui_item *i = ui_items + idx;
|
||||
|
||||
int col = idx % cols;
|
||||
int row = idx / cols;
|
||||
|
||||
i->x = border + col * (gap + item_w);
|
||||
i->y = top + row * (gap + item_h);
|
||||
i->w = item_w;
|
||||
i->h = item_h;
|
||||
i->fill = 0xff755f10;
|
||||
i->text_color = 0xffffffff;
|
||||
i->id = idx;
|
||||
|
||||
snprintf(i->text, sizeof i->text, "%s", items[idx]);
|
||||
|
||||
//printf("ui_item[%d] x=%d y=%d w=%d h=%d text=%s\n", idx, i->x, i->y, i->w, i->h, i->text);
|
||||
}
|
||||
|
||||
int selected = -1;
|
||||
int redraw = 1;
|
||||
|
||||
if (!no_touch) {
|
||||
ret = touchpanel_start(tdev);
|
||||
if (ret < 0) {
|
||||
printf("Failed to start %s, err=%d\n", tdev->name, ret);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
next:
|
||||
while (1) {
|
||||
if (redraw) {
|
||||
ui_draw(ui_items, n_items, &p);
|
||||
video_sync(vdev, true);
|
||||
redraw = 0;
|
||||
}
|
||||
|
||||
if (no_touch)
|
||||
return CMD_RET_SUCCESS;
|
||||
|
||||
// don't be too busy reading i2c
|
||||
udelay(50 * 1000);
|
||||
|
||||
// handle input
|
||||
ret = touchpanel_get_touches(tdev, touches, ARRAY_SIZE(touches));
|
||||
if (ret < 0) {
|
||||
printf("Failed to get touches from %s, err=%d\n", tdev->name, ret);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
/* find first matching tap down */
|
||||
for (int idx = 0; idx < ret; idx++) {
|
||||
int tx = touches[idx].x;
|
||||
int ty = touches[idx].y;
|
||||
|
||||
struct ui_item* hit = ui_hit_find(ui_items, n_items, tx, ty);
|
||||
if (hit) {
|
||||
selected = hit->id;
|
||||
hit->fill = 0xffb19019;
|
||||
redraw = 1;
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected != -1) {
|
||||
// we are done
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof buf, "ret=%d", selected);
|
||||
set_local_var(buf, 1);
|
||||
selected = -1;
|
||||
redraw = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = touchpanel_stop(tdev);
|
||||
if (ret < 0) {
|
||||
printf("Failed to stop %s, err=%d\n", tdev->name, ret);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
return CMD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
static int do_tmenu_render(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
|
||||
{
|
||||
return handle_tmenu(cmdtp, flag, argc, argv, 1);
|
||||
}
|
||||
|
||||
static int do_tmenu_input(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
|
||||
{
|
||||
return handle_tmenu(cmdtp, flag, argc, argv, 0);
|
||||
}
|
||||
|
||||
static int do_tmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
|
||||
{
|
||||
return handle_tmenu(cmdtp, flag, argc, argv, 0);
|
||||
}
|
||||
|
||||
U_BOOT_CMD(tmenu, 30, 1, do_tmenu, "tmenu", "tmenu item1 [item2...] - show touch menu and wait for input");
|
||||
U_BOOT_CMD(tmenu_render, 30, 1, do_tmenu_render, "tmenu_render", "tmenu_render item1 [item2...] - show touch menu");
|
||||
U_BOOT_CMD(tmenu_input, 30, 1, do_tmenu_input, "tmenu_input", "tmenu_input item1 [item2...] - wait for touch menu input");
|
||||
|
||||
#include <pxe_utils.h>
|
||||
#include <extlinux.h>
|
||||
#include <mapmem.h>
|
||||
#include <stdlib.h>
|
||||
#include <image.h>
|
||||
#include <splash.h>
|
||||
#include <sysreset.h>
|
||||
#include <video_console.h>
|
||||
|
||||
int pxe_label_boot(struct pxe_context *ctx, struct pxe_label *label);
|
||||
|
||||
int extlinux_getfile(struct pxe_context *ctx, const char *file_path,
|
||||
char *file_addr, ulong *sizep);
|
||||
|
||||
enum {
|
||||
ACTION_BOOT = 1,
|
||||
ACTION_POWEROFF,
|
||||
ACTION_CONSOLE,
|
||||
ACTION_USB_STORAGE_EMMC,
|
||||
ACTION_USB_STORAGE_SD,
|
||||
};
|
||||
|
||||
struct tmenu_boot_item {
|
||||
int id;
|
||||
char* label;
|
||||
struct bootflow *bflow;
|
||||
|
||||
struct pxe_context *pxe_ctx;
|
||||
struct pxe_label *pxe_label;
|
||||
int action;
|
||||
};
|
||||
|
||||
#define MENU_COLOR_CHOSEN 0xff15801Fu
|
||||
#define MENU_COLOR_HIGHLIGHT 0xffb19019u
|
||||
#define MENU_COLOR_INACTIVE 0xff755f10u
|
||||
#define MENU_COLOR_TEXT 0xffffffffu
|
||||
|
||||
static int do_tmenu_bootflow(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
|
||||
{
|
||||
struct udevice *vdev, *tdev, *cdev;
|
||||
struct video_priv *vpriv;
|
||||
struct touchpanel_touch touches[10];
|
||||
int cmd_ret;
|
||||
int ret;
|
||||
|
||||
start_again:
|
||||
cmd_ret = CMD_RET_FAILURE;
|
||||
|
||||
struct bootstd_priv *std;
|
||||
ret = bootstd_get_priv(&std);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
|
||||
// how many items to reserve
|
||||
int extra_items = 4;
|
||||
|
||||
struct tmenu_boot_item items[64] = {};
|
||||
int n_items = 0;
|
||||
int bmp_loaded = 0;
|
||||
int timeout = 0; // 0 = no timeout, >0 number of 0.1s to wait
|
||||
int autoselect = -1; // which item is autoselected by extlinux.conf (for timeout)
|
||||
|
||||
struct bootflow *bflow;
|
||||
for (ret = bootflow_first_glob(&bflow); !ret; ret = bootflow_next_glob(&bflow)) {
|
||||
if (bflow->state == BOOTFLOWST_READY) {
|
||||
//printf("flow: (%s) %s - %s\n", dev_get_parent(bflow->dev)->name, bflow->os_name ? bflow->os_name : bflow->name, bflow->method->name);
|
||||
|
||||
if (!strcmp(bflow->method->name, "extlinux")) {
|
||||
struct cmd_tbl *cmdtp = calloc(1, sizeof(*cmdtp));
|
||||
struct extlinux_info *pxe_info = calloc(1, sizeof(*pxe_info));
|
||||
struct pxe_context *ctx = calloc(1, sizeof(*ctx));
|
||||
|
||||
if (!ctx || !cmdtp ||!pxe_info)
|
||||
continue;
|
||||
|
||||
ulong addr = map_to_sysmem(bflow->buf);
|
||||
pxe_info->dev = bflow->method;
|
||||
pxe_info->bflow = bflow;
|
||||
int ret = pxe_setup_ctx(ctx, cmdtp, extlinux_getfile, pxe_info, true, bflow->fname, false);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
struct pxe_menu *cfg = parse_pxefile(ctx, addr);
|
||||
if (!cfg) {
|
||||
printf("Error parsing config file\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cfg->bmp && !bmp_loaded && get_pxe_file(ctx, cfg->bmp, image_load_addr) == 1)
|
||||
bmp_loaded = 1;
|
||||
|
||||
if (timeout < cfg->timeout)
|
||||
timeout = cfg->timeout;
|
||||
|
||||
struct list_head *pos;
|
||||
list_for_each(pos, &cfg->labels) {
|
||||
struct pxe_label *label = list_entry(pos, struct pxe_label, list);
|
||||
struct tmenu_boot_item *it = &items[n_items++];
|
||||
|
||||
// select default option for boot
|
||||
if (autoselect < 0 && label->name && cfg->default_label && !strcmp(label->name, cfg->default_label))
|
||||
autoselect = n_items - 1;
|
||||
|
||||
//printf("label %s - %s %s\n", label->num, label->name, label->menu);
|
||||
|
||||
it->id = n_items;
|
||||
it->label = label->menu ? label->menu : label->name;
|
||||
it->bflow = bflow;
|
||||
it->pxe_ctx = ctx;
|
||||
it->pxe_label = label;
|
||||
it->action = ACTION_BOOT;
|
||||
|
||||
if (n_items == (ARRAY_SIZE(items) - extra_items))
|
||||
break;
|
||||
}
|
||||
} else if (!strcmp(bflow->method->name, "efi_mgr")) {
|
||||
// skip efi_mgr
|
||||
} else {
|
||||
struct tmenu_boot_item *it = &items[n_items++];
|
||||
char name[64];
|
||||
|
||||
snprintf(name, sizeof name, "%s", bflow->os_name ? bflow->os_name : bflow->name);
|
||||
|
||||
if (!strcmp(bflow->method->name, "efi")) {
|
||||
// get better name for EFI bootflows
|
||||
if (strstr(bflow->name, "mmc@fe320000")) {
|
||||
snprintf(name, sizeof name, "EFI OS on SD/%d", bflow->part);
|
||||
} else if (strstr(bflow->name, "mmc@fe330000")) {
|
||||
snprintf(name, sizeof name, "EFI OS on eMMC/%d", bflow->part);
|
||||
}
|
||||
}
|
||||
|
||||
it->id = n_items;
|
||||
it->label = strdup(name);
|
||||
it->bflow = bflow;
|
||||
it->action = ACTION_BOOT;
|
||||
}
|
||||
|
||||
if (n_items == (ARRAY_SIZE(items) - extra_items))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct tmenu_boot_item *it = &items[n_items++];
|
||||
|
||||
it->id = n_items;
|
||||
it->label = "U-Boot Console";
|
||||
it->action = ACTION_CONSOLE;
|
||||
|
||||
it = &items[n_items++];
|
||||
|
||||
it->id = n_items;
|
||||
it->label = "eMMC over USB";
|
||||
it->action = ACTION_USB_STORAGE_EMMC;
|
||||
|
||||
it = &items[n_items++];
|
||||
|
||||
//XXX: only if SD card is detected
|
||||
it->id = n_items;
|
||||
it->label = "SD over USB";
|
||||
it->action = ACTION_USB_STORAGE_SD;
|
||||
|
||||
it = &items[n_items++];
|
||||
|
||||
it->id = n_items;
|
||||
it->label = "Power off";
|
||||
it->action = ACTION_POWEROFF;
|
||||
|
||||
ret = uclass_first_device_err(UCLASS_VIDEO, &vdev);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
|
||||
ret = uclass_first_device_err(UCLASS_TOUCHPANEL, &tdev);
|
||||
if (ret)
|
||||
return CMD_RET_FAILURE;
|
||||
|
||||
vpriv = dev_get_uclass_priv(vdev);
|
||||
|
||||
if (vpriv->bpix != VIDEO_BPP32) {
|
||||
printf("tmenu requires 32BPP video device\n");
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
/* prep done, start doing the UI work */
|
||||
|
||||
env_set("stdout", "serial");
|
||||
env_set("stderr", "serial");
|
||||
|
||||
if (bmp_loaded) {
|
||||
video_clear(vdev);
|
||||
bmp_display(image_load_addr, BMP_ALIGN_CENTER, BMP_ALIGN_CENTER);
|
||||
}
|
||||
|
||||
struct painter p = {
|
||||
.fb = vpriv->fb,
|
||||
.fb_end = vpriv->fb + vpriv->fb_size,
|
||||
.cur = vpriv->fb,
|
||||
.line_length = vpriv->line_length,
|
||||
.bpp = VNBYTES(vpriv->bpix),
|
||||
.cols = vpriv->xsize,
|
||||
.rows = vpriv->ysize,
|
||||
};
|
||||
|
||||
// prepare ui_items and lay them out
|
||||
struct ui_item ui_items[n_items];
|
||||
int border = 40, max_total_h = min(700, vpriv->ysize - 2 * border);
|
||||
int gap = 10;
|
||||
|
||||
int cols = 1;
|
||||
while ((max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / DIV_ROUND_UP(n_items, cols) < 100)
|
||||
cols++;
|
||||
int rows = DIV_ROUND_UP(n_items, cols);
|
||||
|
||||
int item_h = (max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / rows;
|
||||
int item_w = (vpriv->xsize - 2 * border - (cols - 1) * gap) / cols;
|
||||
|
||||
int top = vpriv->ysize - border - rows * (item_h + gap) - gap;
|
||||
|
||||
for (int idx = 0; idx < n_items; idx++) {
|
||||
struct ui_item *i = ui_items + idx;
|
||||
|
||||
int col = idx % cols;
|
||||
int row = idx / cols;
|
||||
|
||||
i->x = border + col * (gap + item_w);
|
||||
i->y = top + row * (gap + item_h);
|
||||
i->w = item_w;
|
||||
i->h = item_h;
|
||||
i->fill = MENU_COLOR_INACTIVE;
|
||||
i->text_color = MENU_COLOR_TEXT;
|
||||
i->id = idx;
|
||||
|
||||
snprintf(i->text, sizeof i->text, "%s", items[idx].label);
|
||||
|
||||
//printf("ui_item[%d] x=%d y=%d w=%d h=%d text=%s\n", idx, i->x, i->y, i->w, i->h, i->text);
|
||||
}
|
||||
|
||||
int selected = -1;
|
||||
int redraw = 1;
|
||||
int highlighted = autoselect >= 0 ? autoselect : 0;
|
||||
|
||||
ret = touchpanel_start(tdev);
|
||||
if (ret < 0) {
|
||||
printf("Failed to start %s, err=%d\n", tdev->name, ret);
|
||||
goto out_restore_console;
|
||||
}
|
||||
|
||||
struct udevice *led_g = NULL, *led_r = NULL, *led_b = NULL;
|
||||
uclass_get_device_by_name(UCLASS_LED, "led-red", &led_r);
|
||||
uclass_get_device_by_name(UCLASS_LED, "led-green", &led_g);
|
||||
uclass_get_device_by_name(UCLASS_LED, "led-blue", &led_b);
|
||||
|
||||
enum { VOL_UP, VOL_DOWN, POWER };
|
||||
struct btn { struct udevice *dev; int prev; int cur; int press; int release; } btns[3] = {};
|
||||
button_get_by_label("Volume Up", &btns[VOL_UP].dev);
|
||||
button_get_by_label("Volume Down", &btns[VOL_DOWN].dev);
|
||||
button_get_by_label("Power", &btns[POWER].dev);
|
||||
|
||||
int cycles = 0;
|
||||
next:
|
||||
while (1) {
|
||||
for (int i = 0; i < ARRAY_SIZE(btns); i++) {
|
||||
struct btn* b = &btns[i];
|
||||
b->prev = b->cur;
|
||||
b->cur = b->dev && button_get_state(b->dev) == BUTTON_ON;
|
||||
b->press = b->cur && !b->prev;
|
||||
b->release = !b->cur && b->prev;
|
||||
|
||||
// any button press cancels the timeout
|
||||
if (b->cur)
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
// menu up/down navigation feedback
|
||||
if (btns[VOL_UP].press) {
|
||||
highlighted--;
|
||||
if (highlighted < 0)
|
||||
highlighted = n_items - 1;
|
||||
redraw = true;
|
||||
} else if (btns[VOL_DOWN].press) {
|
||||
highlighted = (highlighted + 1) % n_items;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (redraw)
|
||||
for (int i = 0; i < n_items; i++)
|
||||
ui_items[i].fill = i == highlighted ? MENU_COLOR_HIGHLIGHT : MENU_COLOR_INACTIVE;
|
||||
|
||||
// power button press
|
||||
if (btns[POWER].press) {
|
||||
ui_items[highlighted].fill = MENU_COLOR_CHOSEN;
|
||||
redraw = true;
|
||||
if (led_g)
|
||||
led_set_state(led_g, LEDST_ON);
|
||||
} else if (btns[POWER].release) {
|
||||
selected = highlighted;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
// handle autoselect timeout
|
||||
if (autoselect >= 0 && timeout > 0 && timeout == cycles / 2) {
|
||||
selected = autoselect;
|
||||
ui_items[selected].fill = MENU_COLOR_CHOSEN;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
cycles++;
|
||||
|
||||
// UI drawing
|
||||
if (redraw) {
|
||||
ui_draw(ui_items, n_items, &p);
|
||||
video_sync(vdev, true);
|
||||
redraw = 0;
|
||||
}
|
||||
|
||||
// don't be too busy reading i2c
|
||||
udelay(50 * 1000);
|
||||
|
||||
// handle input
|
||||
ret = touchpanel_get_touches(tdev, touches, ARRAY_SIZE(touches));
|
||||
if (ret < 0) {
|
||||
printf("Failed to get touches from %s, err=%d\n", tdev->name, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
// cancel timeout on touch
|
||||
if (ret > 0)
|
||||
timeout = 0;
|
||||
|
||||
// find first matching tap down
|
||||
for (int idx = 0; idx < ret; idx++) {
|
||||
int tx = touches[idx].x;
|
||||
int ty = touches[idx].y;
|
||||
|
||||
struct ui_item* hit = ui_hit_find(ui_items, n_items, tx, ty);
|
||||
if (hit) {
|
||||
selected = hit->id;
|
||||
hit->fill = MENU_COLOR_CHOSEN;
|
||||
redraw = 1;
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
|
||||
// final selection processing
|
||||
if (selected != -1) {
|
||||
struct tmenu_boot_item *it = &items[selected];
|
||||
|
||||
if (it->action == ACTION_CONSOLE) {
|
||||
cmd_ret = CMD_RET_SUCCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (it->action == ACTION_USB_STORAGE_EMMC || it->action == ACTION_USB_STORAGE_SD) {
|
||||
cmd_ret = CMD_RET_SUCCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (it->action == ACTION_POWEROFF) {
|
||||
ret = sysreset_walk(SYSRESET_POWER_OFF);
|
||||
if (ret == -EINPROGRESS)
|
||||
mdelay(1000);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bmp_loaded) {
|
||||
video_clear(vdev);
|
||||
bmp_display(image_load_addr, BMP_ALIGN_CENTER, BMP_ALIGN_CENTER);
|
||||
}
|
||||
|
||||
std->cur_bootflow = it->bflow;
|
||||
|
||||
if (it->pxe_label) {
|
||||
pxe_label_boot(it->pxe_ctx, it->pxe_label);
|
||||
} else {
|
||||
bootflow_boot(it->bflow);
|
||||
}
|
||||
|
||||
selected = -1;
|
||||
redraw = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//out_stop_touch:
|
||||
ret = touchpanel_stop(tdev);
|
||||
if (ret < 0)
|
||||
printf("Failed to stop %s, err=%d\n", tdev->name, ret);
|
||||
|
||||
out_restore_console:
|
||||
//video_clear(vdev);
|
||||
if (!uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &cdev))
|
||||
vidconsole_clear_and_reset(cdev);
|
||||
|
||||
env_set("stdout", "serial,vidconsole");
|
||||
env_set("stderr", "serial,vidconsole");
|
||||
|
||||
if (selected >= 0) {
|
||||
struct tmenu_boot_item *it = &items[selected];
|
||||
|
||||
if (it->action == ACTION_USB_STORAGE_EMMC) {
|
||||
cli_simple_run_command("ums 0 mmc 0", 0);
|
||||
cli_simple_run_command("bootflow scan", 0);
|
||||
goto start_again;
|
||||
} else if (it->action == ACTION_USB_STORAGE_SD) {
|
||||
cli_simple_run_command("ums 0 mmc 1", 0);
|
||||
cli_simple_run_command("bootflow scan", 0);
|
||||
goto start_again;
|
||||
}
|
||||
}
|
||||
|
||||
return cmd_ret;
|
||||
}
|
||||
|
||||
U_BOOT_CMD(tmenu_bootflow, 4, 1, do_tmenu_bootflow, "tmenu_bootflow", "tmenu_bootflow - show bootflow menu");
|
Reference in New Issue
Block a user