uboot/boot/scene_menu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Implementation of a menu in a scene
   4 *
   5 * Copyright 2022 Google LLC
   6 * Written by Simon Glass <sjg@chromium.org>
   7 */
   8
   9#define LOG_CATEGORY    LOGC_BOOT
  10
  11#include <common.h>
  12#include <dm.h>
  13#include <expo.h>
  14#include <malloc.h>
  15#include <mapmem.h>
  16#include <menu.h>
  17#include <video.h>
  18#include <video_console.h>
  19#include <linux/input.h>
  20#include "scene_internal.h"
  21
  22static void scene_menuitem_destroy(struct scene_menitem *item)
  23{
  24        free(item->name);
  25        free(item);
  26}
  27
  28void scene_menu_destroy(struct scene_obj_menu *menu)
  29{
  30        struct scene_menitem *item, *next;
  31
  32        list_for_each_entry_safe(item, next, &menu->item_head, sibling)
  33                scene_menuitem_destroy(item);
  34}
  35
  36/**
  37 * menu_point_to_item() - Point to a particular menu item
  38 *
  39 * Sets the currently pointed-to / highlighted menu item
  40 */
  41static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
  42{
  43        menu->cur_item_id = item_id;
  44}
  45
  46int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
  47{
  48        struct scene_menitem *item;
  49        int y, cur_y;
  50        int ret;
  51
  52        y = menu->obj.y;
  53        if (menu->title_id) {
  54                ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
  55                if (ret < 0)
  56                        return log_msg_ret("tit", ret);
  57
  58                ret = scene_obj_get_hw(scn, menu->title_id, NULL);
  59                if (ret < 0)
  60                        return log_msg_ret("hei", ret);
  61
  62                y += ret * 2;
  63        }
  64
  65        /*
  66         * Currently everything is hard-coded to particular columns so this
  67         * won't work on small displays and looks strange if the font size is
  68         * small. This can be updated once text measuring is supported in
  69         * vidconsole
  70         */
  71        cur_y = -1;
  72        list_for_each_entry(item, &menu->item_head, sibling) {
  73                int height;
  74
  75                ret = scene_obj_get_hw(scn, item->desc_id, NULL);
  76                if (ret < 0)
  77                        return log_msg_ret("get", ret);
  78                height = ret;
  79
  80                if (item->flags & SCENEMIF_GAP_BEFORE)
  81                        y += height;
  82
  83                /* select an item if not done already */
  84                if (!menu->cur_item_id)
  85                        menu_point_to_item(menu, item->id);
  86
  87                /*
  88                 * Put the label on the left, then leave a space for the
  89                 * pointer, then the key and the description
  90                 */
  91                if (item->label_id) {
  92                        ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
  93                                                y);
  94                        if (ret < 0)
  95                                return log_msg_ret("nam", ret);
  96                }
  97
  98                ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
  99                                        y);
 100                if (ret < 0)
 101                        return log_msg_ret("key", ret);
 102
 103                ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
 104                                        y);
 105                if (ret < 0)
 106                        return log_msg_ret("des", ret);
 107
 108                if (menu->cur_item_id == item->id)
 109                        cur_y = y;
 110
 111                if (item->preview_id) {
 112                        bool hide;
 113
 114                        /*
 115                         * put all previews on top of each other, on the right
 116                         * size of the display
 117                         */
 118                        ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
 119                        if (ret < 0)
 120                                return log_msg_ret("prev", ret);
 121
 122                        hide = menu->cur_item_id != item->id;
 123                        ret = scene_obj_set_hide(scn, item->preview_id, hide);
 124                        if (ret < 0)
 125                                return log_msg_ret("hid", ret);
 126                }
 127
 128                y += height;
 129        }
 130
 131        if (menu->pointer_id && cur_y != -1) {
 132                /*
 133                 * put the pointer to the right of and level with the item it
 134                 * points to
 135                 */
 136                ret = scene_obj_set_pos(scn, menu->pointer_id,
 137                                        menu->obj.x + 200, cur_y);
 138                if (ret < 0)
 139                        return log_msg_ret("ptr", ret);
 140        }
 141
 142        return 0;
 143}
 144
 145int scene_menu(struct scene *scn, const char *name, uint id,
 146               struct scene_obj_menu **menup)
 147{
 148        struct scene_obj_menu *menu;
 149        int ret;
 150
 151        ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
 152                            sizeof(struct scene_obj_menu),
 153                            (struct scene_obj **)&menu);
 154        if (ret < 0)
 155                return log_msg_ret("obj", -ENOMEM);
 156
 157        if (menup)
 158                *menup = menu;
 159        INIT_LIST_HEAD(&menu->item_head);
 160
 161        ret = scene_menu_arrange(scn, menu);
 162        if (ret)
 163                return log_msg_ret("pos", ret);
 164
 165        return menu->obj.id;
 166}
 167
 168static struct scene_menitem *scene_menu_find_key(struct scene *scn,
 169                                                  struct scene_obj_menu *menu,
 170                                                  int key)
 171{
 172        struct scene_menitem *item;
 173
 174        list_for_each_entry(item, &menu->item_head, sibling) {
 175                if (item->key_id) {
 176                        struct scene_obj_txt *txt;
 177                        const char *str;
 178
 179                        txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
 180                        if (txt) {
 181                                str = expo_get_str(scn->expo, txt->str_id);
 182                                if (str && *str == key)
 183                                        return item;
 184                        }
 185                }
 186        }
 187
 188        return NULL;
 189}
 190
 191int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
 192                        struct expo_action *event)
 193{
 194        struct scene_menitem *item, *cur, *key_item;
 195
 196        cur = NULL;
 197        key_item = NULL;
 198
 199        if (!list_empty(&menu->item_head)) {
 200                list_for_each_entry(item, &menu->item_head, sibling) {
 201                        /* select an item if not done already */
 202                        if (menu->cur_item_id == item->id) {
 203                                cur = item;
 204                                break;
 205                        }
 206                }
 207        }
 208
 209        if (!cur)
 210                return -ENOTTY;
 211
 212        switch (key) {
 213        case BKEY_UP:
 214                if (item != list_first_entry(&menu->item_head,
 215                                             struct scene_menitem, sibling)) {
 216                        item = list_entry(item->sibling.prev,
 217                                          struct scene_menitem, sibling);
 218                        event->type = EXPOACT_POINT;
 219                        event->select.id = item->id;
 220                        log_debug("up to item %d\n", event->select.id);
 221                }
 222                break;
 223        case BKEY_DOWN:
 224                if (!list_is_last(&item->sibling, &menu->item_head)) {
 225                        item = list_entry(item->sibling.next,
 226                                          struct scene_menitem, sibling);
 227                        event->type = EXPOACT_POINT;
 228                        event->select.id = item->id;
 229                        log_debug("down to item %d\n", event->select.id);
 230                }
 231                break;
 232        case BKEY_SELECT:
 233                event->type = EXPOACT_SELECT;
 234                event->select.id = item->id;
 235                log_debug("select item %d\n", event->select.id);
 236                break;
 237        case BKEY_QUIT:
 238                event->type = EXPOACT_QUIT;
 239                log_debug("quit\n");
 240                break;
 241        case '0'...'9':
 242                key_item = scene_menu_find_key(scn, menu, key);
 243                if (key_item) {
 244                        event->type = EXPOACT_SELECT;
 245                        event->select.id = key_item->id;
 246                }
 247                break;
 248        }
 249
 250        menu_point_to_item(menu, item->id);
 251
 252        return 0;
 253}
 254
 255int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
 256                   uint key_id, uint label_id, uint desc_id, uint preview_id,
 257                   uint flags, struct scene_menitem **itemp)
 258{
 259        struct scene_obj_menu *menu;
 260        struct scene_menitem *item;
 261        int ret;
 262
 263        menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
 264        if (!menu)
 265                return log_msg_ret("find", -ENOENT);
 266
 267        /* Check that the text ID is valid */
 268        if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
 269                return log_msg_ret("txt", -EINVAL);
 270
 271        item = calloc(1, sizeof(struct scene_obj_menu));
 272        if (!item)
 273                return log_msg_ret("item", -ENOMEM);
 274        item->name = strdup(name);
 275        if (!item->name) {
 276                free(item);
 277                return log_msg_ret("name", -ENOMEM);
 278        }
 279
 280        item->id = resolve_id(scn->expo, id);
 281        item->key_id = key_id;
 282        item->label_id = label_id;
 283        item->desc_id = desc_id;
 284        item->preview_id = preview_id;
 285        item->flags = flags;
 286        list_add_tail(&item->sibling, &menu->item_head);
 287
 288        ret = scene_menu_arrange(scn, menu);
 289        if (ret)
 290                return log_msg_ret("pos", ret);
 291
 292        if (itemp)
 293                *itemp = item;
 294
 295        return item->id;
 296}
 297
 298int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
 299{
 300        struct scene_obj_menu *menu;
 301        struct scene_obj_txt *txt;
 302
 303        menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
 304        if (!menu)
 305                return log_msg_ret("menu", -ENOENT);
 306
 307        /* Check that the ID is valid */
 308        if (title_id) {
 309                txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
 310                if (!txt)
 311                        return log_msg_ret("txt", -EINVAL);
 312        }
 313
 314        menu->title_id = title_id;
 315
 316        return 0;
 317}
 318
 319int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
 320{
 321        struct scene_obj_menu *menu;
 322        struct scene_obj *obj;
 323
 324        menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
 325        if (!menu)
 326                return log_msg_ret("menu", -ENOENT);
 327
 328        /* Check that the ID is valid */
 329        if (pointer_id) {
 330                obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
 331                if (!obj)
 332                        return log_msg_ret("obj", -EINVAL);
 333        }
 334
 335        menu->pointer_id = pointer_id;
 336
 337        return 0;
 338}
 339
 340int scene_menu_display(struct scene_obj_menu *menu)
 341{
 342        struct scene *scn = menu->obj.scene;
 343        struct scene_obj_txt *pointer;
 344        struct expo *exp = scn->expo;
 345        struct scene_menitem *item;
 346        const char *pstr;
 347
 348        printf("U-Boot    :    Boot Menu\n\n");
 349        if (menu->title_id) {
 350                struct scene_obj_txt *txt;
 351                const char *str;
 352
 353                txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
 354                if (!txt)
 355                        return log_msg_ret("txt", -EINVAL);
 356
 357                str = expo_get_str(exp, txt->str_id);
 358                printf("%s\n\n", str);
 359        }
 360
 361        if (list_empty(&menu->item_head))
 362                return 0;
 363
 364        pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
 365        pstr = expo_get_str(scn->expo, pointer->str_id);
 366
 367        list_for_each_entry(item, &menu->item_head, sibling) {
 368                struct scene_obj_txt *key = NULL, *label = NULL;
 369                struct scene_obj_txt *desc = NULL;
 370                const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
 371
 372                key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
 373                if (key)
 374                        kstr = expo_get_str(exp, key->str_id);
 375
 376                label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
 377                if (label)
 378                        lstr = expo_get_str(exp, label->str_id);
 379
 380                desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
 381                if (desc)
 382                        dstr = expo_get_str(exp, desc->str_id);
 383
 384                printf("%3s  %3s  %-10s  %s\n",
 385                       pointer && menu->cur_item_id == item->id ? pstr : "",
 386                       kstr, lstr, dstr);
 387        }
 388
 389        return -ENOTSUPP;
 390}
 391