Expo menu

U-Boot provides a menu implementation for use with selecting bootflows and changing U-Boot settings. This is in early stages of development.

Motivation

U-Boot already has a text-based menu system accessed via the bootmenu command. This works using environment variables, or via some EFI-specific hacks.

The command makes use of a lower-level menu implementation, which is quite flexible and can be used to make menu hierarchies.

However this system is not flexible enough for use with standard boot. It does not support a graphical user interface and cannot currently support anything more than a very simple list of items. While it does support multiple menus in hierarchies, these are implemented by the caller. See for example eficonfig.c.

Another challenge with the current menu implementation is that it controls the event loop, such that bootmenu_loop() does not return until a key is pressed. This makes it difficult to implement dynamic displays or to do other things while the menu is running, such as searching for more bootflows.

For these reasons an attempt has been made to develop a more flexible system which can handle menus as well as other elements. This is called ‘expo’, short for exposition, in an attempt to avoid common words like display, screen, menu and the like. The primary goal is to support Verified Boot for Embedded (VBE), although it is available to any boot method, using the ‘bootflow menu’ command.

Efforts have been made to use common code with the existing menu, including key processing in particular.

Previous work looked at integrating Nuklear into U-Boot. This works fine and could provide a way to provide a more flexible UI, perhaps with expo dealing with the interface to Nuklear. But this is quite a big step and it may be years before this becomes desirable, if at all. For now, U-Boot only needs a fairly simple set of menus and options, so rendering them directly is fairly straightforward.

Concepts

The creator of the expo is here called a controller and it controls most aspects of the expo. This is the code that you must write to use expo.

An expo is a set of scenes which can be presented to the user one at a time, to show information and obtain input from the user.

A scene is a collection of objects which are displayed together on the screen. Only one scene is visible at a time and scenes do not share objects.

A scene object is something that appears in the scene, such as some text, an image or a menu. Objects can be positioned and hidden.

A menu object contains a title, a set of menu items and a pointer to the current item. Menu items consist of a keypress (indicating what to press to select the item), label and description. All three are shown in a single line within the menu. Items can also have a preview image, which is shown when the item is highlighted.

All components have a name. This is purely for debugging, so it is easy to see what object is referred to. Of course the ID numbers can help as well, but they are less easy to distinguish.

While the expo implementation provides support for handling keypresses and rendering on the display or serial port, it does not actually deal with reading input from the user, nor what should be done when a particular menu item is selected. This is deliberate since having the event loop outside the expo is more flexible, particularly in a single-threaded environment like U-Boot.

Everything within an expo has a unique ID number. This is done so that it is easy to refer to things after the expo has been created. The expectation is that the controller declares an enum containing all of the elements in the expo, passing the ID of each object as it is created. When a menu item is selected, its ID is returned. When a object’s font or position needs to change, the ID is passed to expo functions to indicate which object it is. It is possible for expo to auto-allocate IDs, but this is not recommended. The use of IDs is a convenience, removing the need for the controller to store pointers to objects, or even the IDs of objects. Programmatic creation of many items in a loop can be handled by allocating space in the enum for a maximum number of items, then adding the loop count to the enum values to obtain unique IDs.

All text strings are stored in a structure attached to the expo, referenced by a text ID. This makes it easier at some point to implement multiple languages or to support Unicode strings.

Menu objects do not have their own text and image objects. Instead they simply refer to objects which have been created. So a menu item is just a collection of IDs of text and image objects. When adding a menu item you must create these objects first, then create the menu item, passing in the relevant IDs.

Creating an expo

To create an expo, use expo_new() followed by scene_new() to create a scene. Then add objects to the scene, using functions like scene_txt_str() and scene_menu(). For every menu item, add text and image objects, then create the menu item with scene_menuitem(), referring to those objects.

Layout

Individual objects can be positioned using scene_obj_set_pos(). Menu items cannot be positioned manually: this is done by scene_arrange() which is called automatically when something changes. The menu itself determines the position of its items.

Rendering

Rendering is performed by calling expo_render(). This uses either the vidconsole, if present, or the serial console in text mode. Expo handles presentation automatically in either case, without any change in how the expo is created.

For the vidconsole, Truetype fonts can be used if enabled, to enhance the quality of the display. For text mode, each menu item is shown in a single line, allowing easy selection using arrow keys.

Input

The controller is responsible for collecting keyboard input. A good way to do this is to use cli_ch_process(), since it handles conversion of escape sequences into keys. However, expo has some special menu-key codes for navigating the interface. These are defined in enum bootmenu_key and include BKEY_UP for moving up and BKEY_SELECT for selecting an item. You can use bootmenu_conv_key() to convert an ASCII key into one of these.

Once a keypress is decoded, call expo_send_key() to send it to the expo. This may cause an update to the expo state and may produce an action.

Actions

Call expo_action_get() in the event loop to check for any actions that the expo wants to report. These can include selecting a particular menu item, or quitting the menu. Processing of these is the responsibility of your controller.

Event loop

Expo is intended to be used in an event loop. For an example loop, see bootflow_menu_run(). It is possible to perform other work in your event loop, such as scanning devices for more bootflows.

Themes

Expo does not itself support themes. The bootflow_menu implement supposed a basic theme, applying font sizes to the various text objects in the expo.

API documentation

enum expoact_type

types of actions reported by the expo

Constants

EXPOACT_NONE

no action

EXPOACT_POINT

menu item was highlighted (id indicates which)

EXPOACT_SELECT

menu item was selected (id indicates which)

EXPOACT_QUIT

request to exit the menu

struct expo_action

an action report by the expo

Definition

struct expo_action {
  enum expoact_type type;
  union {
    struct {
      int id;
    } select;
  };
};

Members

type

Action type (EXPOACT_NONE if there is no action)

{unnamed_union}

anonymous

select

Used for EXPOACT_POINT and EXPOACT_SELECT

struct expo

information about an expo

Definition

struct expo {
  char *name;
  struct udevice *display;
  uint scene_id;
  uint next_id;
  struct expo_action action;
  bool text_mode;
  void *priv;
  struct list_head scene_head;
  struct list_head str_head;
};

Members

name

Name of the expo (allocated)

display

Display to use (UCLASS_VIDEO), or NULL to use text mode

scene_id

Current scene ID (0 if none)

next_id

Next ID number to use, for automatic allocation

action

Action selected by user. At present only one is supported, with the type set to EXPOACT_NONE if there is no action

text_mode

true to use text mode for the menu (no vidconsole)

priv

Private data for the controller

scene_head

List of scenes

str_head

list of strings

Description

A group of scenes which can be presented to the user, typically to obtain input or to make a selection.

struct expo_string

a string that can be used in an expo

Definition

struct expo_string {
  uint id;
  const char *str;
  struct list_head sibling;
};

Members

id

ID number of the string

str

String

sibling

Node to link this object to its siblings

struct scene

information about a scene in an expo

Definition

struct scene {
  struct expo *expo;
  char *name;
  uint id;
  char *title;
  struct list_head sibling;
  struct list_head obj_head;
};

Members

expo

Expo this scene is part of

name

Name of the scene (allocated)

id

ID number of the scene

title

Title of the scene (allocated)

sibling

Node to link this scene to its siblings

obj_head

List of objects in the scene

Description

A collection of text/image/menu items in an expo

enum scene_obj_t

type of a scene object

Constants

SCENEOBJT_NONE

Used to indicate that the type does not matter

SCENEOBJT_IMAGE

Image data to render

SCENEOBJT_TEXT

Text line to render

SCENEOBJT_MENU

Menu containing items the user can select

struct scene_obj

information about an object in a scene

Definition

struct scene_obj {
  struct scene *scene;
  char *name;
  uint id;
  enum scene_obj_t type;
  int x;
  int y;
  bool hide;
  struct list_head sibling;
};

Members

scene

Scene that this object relates to

name

Name of the object (allocated)

id

ID number of the object

type

Type of this object

x

x position, in pixels from left side

y

y position, in pixels from top

hide

true if the object should be hidden

sibling

Node to link this object to its siblings

struct scene_obj_img

information about an image object in a scene

Definition

struct scene_obj_img {
  struct scene_obj obj;
  char *data;
};

Members

obj

Basic object information

data

Image data in BMP format

Description

This is a rectangular image which is blitted onto the display

struct scene_obj_txt

information about a text object in a scene

Definition

struct scene_obj_txt {
  struct scene_obj obj;
  uint str_id;
  const char *font_name;
  uint font_size;
};

Members

obj

Basic object information

str_id

ID of the text string to display

font_name

Name of font (allocated by caller)

font_size

Nominal size of font in pixels

Description

This is a single-line text object

struct scene_obj_menu

information about a menu object in a scene

Definition

struct scene_obj_menu {
  struct scene_obj obj;
  uint title_id;
  uint cur_item_id;
  uint pointer_id;
  struct list_head item_head;
};

Members

obj

Basic object information

title_id

ID of the title text, or 0 if none

cur_item_id

ID of the current menu item, or 0 if none

pointer_id

ID of the object pointing to the current selection

item_head

List of items in the menu

Description

A menu has a number of items which can be selected by the user

It also has:

  • a text/image object (pointer_id) which points to the current item (cur_item_id)

  • a preview object which shows an image related to the current item

enum scene_menuitem_flags_t

flags for menu items

Constants

SCENEMIF_GAP_BEFORE

Add a gap before this item

struct scene_menitem

a menu item in a menu

Definition

struct scene_menitem {
  char *name;
  uint id;
  uint key_id;
  uint label_id;
  uint desc_id;
  uint preview_id;
  uint flags;
  struct list_head sibling;
};

Members

name

Name of the item (this is allocated by this call)

id

ID number of the object

key_id

ID of text object to use as the keypress to show

label_id

ID of text object to use as the label text

desc_id

ID of text object to use as the description text

preview_id

ID of the preview object, or 0 if none

flags

Flags for this item

sibling

Node to link this item to its siblings

Description

A menu item has:

  • text object holding the name (short) and description (can be longer)

  • a text object holding the keypress

int expo_new(const char *name, void *priv, struct expo **expp)

create a new expo

Parameters

const char *name

Name of expo (this is allocated by this call)

void *priv

Private data for the controller

struct expo **expp

Returns a pointer to the new expo on success

Description

Allocates a new expo

Return

0 if OK, -ENOMEM if out of memory

void expo_destroy(struct expo *exp)

Destroy an expo and free all its memory

Parameters

struct expo *exp

Expo to destroy

int expo_str(struct expo *exp, const char *name, uint id, const char *str)

add a new string to an expo

Parameters

struct expo *exp

Expo to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

const char *str

Pointer to text to display (allocated by caller)

Return

ID number for the object (typically id), or -ve on error

const char *expo_get_str(struct expo *exp, uint id)

Get a string by ID

Parameters

struct expo *exp

Expo to use

uint id

String ID to look up returns string, or NULL if not found

int expo_set_display(struct expo *exp, struct udevice *dev)

set the display to use for a expo

Parameters

struct expo *exp

Expo to update

struct udevice *dev

Display to use (UCLASS_VIDEO), NULL to use text mode

Return

0 (always)

int expo_set_scene_id(struct expo *exp, uint scene_id)

Set the current scene ID

Parameters

struct expo *exp

Expo to update

uint scene_id

New scene ID to use (0 to select no scene)

Return

0 if OK, -ENOENT if there is no scene with that ID

int expo_render(struct expo *exp)

render the expo on the display / console

Parameters

struct expo *exp

Expo to render

Return

0 if OK, -ECHILD if there is no current scene, -ENOENT if the current scene is not found, other error if something else goes wrong

void exp_set_text_mode(struct expo *exp, bool text_mode)

Controls whether the expo renders in text mode

Parameters

struct expo *exp

Expo to update

bool text_mode

true to use text mode, false to use the console

int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)

create a new scene in a expo

Parameters

struct expo *exp

Expo to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new scene (0 to allocate one)

struct scene **scnp

Returns a pointer to the new scene on success

Description

The scene is given the ID id which must be unique across all scenes, objects and items. The expo’s next_id is updated to at least id + 1

Return

ID number for the scene (typically id), or -ve on error

struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)

Look up a scene by ID

Parameters

struct expo *exp

Expo to check

uint scene_id

Scene ID to look up returns pointer to scene if found, else NULL

int scene_title_set(struct scene *scn, const char *title)

set the scene title

Parameters

struct scene *scn

Scene to update

const char *title

Title to set, NULL if none (this is allocated by this call)

Return

0 if OK, -ENOMEM if out of memory

int scene_obj_count(struct scene *scn)

Count the number of objects in a scene

Parameters

struct scene *scn

Scene to check

Return

number of objects in the scene, 0 if none

int scene_img(struct scene *scn, const char *name, uint id, char *data, struct scene_obj_img **imgp)

add a new image to a scene

Parameters

struct scene *scn

Scene to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

char *data

Pointer to image data

struct scene_obj_img **imgp

If non-NULL, returns the new object

Return

ID number for the object (typically id), or -ve on error

int scene_txt(struct scene *scn, const char *name, uint id, uint str_id, struct scene_obj_txt **txtp)

add a new text object to a scene

Parameters

struct scene *scn

Scene to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

uint str_id

ID of the string to use

struct scene_obj_txt **txtp

If non-NULL, returns the new object

Return

ID number for the object (typically id), or -ve on error

int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id, const char *str, struct scene_obj_txt **txtp)

add a new string to expr and text object to a scene

Parameters

struct scene *scn

Scene to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

uint str_id

ID of the string to use

const char *str

Pointer to text to display (allocated by caller)

struct scene_obj_txt **txtp

If non-NULL, returns the new object

Return

ID number for the object (typically id), or -ve on error

int scene_menu(struct scene *scn, const char *name, uint id, struct scene_obj_menu **menup)

create a menu

Parameters

struct scene *scn

Scene to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

struct scene_obj_menu **menup

If non-NULL, returns the new object

Return

ID number for the object (typically id), or -ve on error

int scene_txt_set_font(struct scene *scn, uint id, const char *font_name, uint font_size)

Set the font for an object

Parameters

struct scene *scn

Scene to update

uint id

ID of object to update

const char *font_name

Font name to use (allocated by caller)

uint font_size

Font size to use (nominal height in pixels)

int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)

Set the postion of an object

Parameters

struct scene *scn

Scene to update

uint id

ID of object to update

int x

x position, in pixels from left side

int y

y position, in pixels from top

Return

0 if OK, -ENOENT if id is invalid

int scene_obj_set_hide(struct scene *scn, uint id, bool hide)

Set whether an object is hidden

Parameters

struct scene *scn

Scene to update

uint id

ID of object to update

bool hide

true to hide the object, false to show it

Description

The update happens when the expo is next rendered.

Return

0 if OK, -ENOENT if id is invalid

int scene_menu_set_title(struct scene *scn, uint id, uint title_id)

Set the title of a menu

Parameters

struct scene *scn

Scene to update

uint id

ID of menu object to update

uint title_id

ID of text object to use as the title

Return

0 if OK, -ENOENT if id is invalid, -EINVAL if title_id is invalid

int scene_menu_set_pointer(struct scene *scn, uint id, uint cur_item_id)

Set the item pointer for a menu

Parameters

struct scene *scn

Scene to update

uint id

ID of menu object to update

uint cur_item_id

ID of text or image object to use as a pointer to the current item

Description

This is a visual indicator of the current item, typically a “>” character which sits next to the current item and moves when the user presses the up/down arrow keys

Return

0 if OK, -ENOENT if id is invalid, -EINVAL if cur_item_id is invalid

int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)

Get width and height of an object in a scene

Parameters

struct scene *scn

Scene to check

uint id

ID of menu object to check

int *widthp

If non-NULL, returns width of object in pixels

Return

Height of object in pixels

int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id, uint key_id, uint label_id, uint desc_id, uint preview_id, uint flags, struct scene_menitem **itemp)

Add an item to a menu

Parameters

struct scene *scn

Scene to update

uint menu_id

ID of menu object to update

const char *name

Name to use (this is allocated by this call)

uint id

ID to use for the new object (0 to allocate one)

uint key_id

ID of text object to use as the keypress to show

uint label_id

ID of text object to use as the label text

uint desc_id

ID of text object to use as the description text

uint preview_id

ID of object to use as the preview (text or image)

uint flags

Flags for this item (enum scene_menuitem_flags_t)

struct scene_menitem **itemp

If non-NULL, returns the new object

Return

ID number for the item (typically id), or -ve on error

int scene_arrange(struct scene *scn)

Arrange the scene to deal with object sizes

Parameters

struct scene *scn

Scene to arrange

Description

Updates any menus in the scene so that their objects are in the right place.

Return

0 if OK, -ve on error

int expo_send_key(struct expo *exp, int key)

set a keypress to the expo

Parameters

struct expo *exp

Expo to receive the key

int key

Key to send (ASCII or enum bootmenu_key)

Return

0 if OK, -ECHILD if there is no current scene

int expo_action_get(struct expo *exp, struct expo_action *act)

read user input from the expo

Parameters

struct expo *exp

Expo to check

struct expo_action *act

Returns action

Return

0 if OK, -EAGAIN if there was no action to return

Future ideas

Some ideas for future work:

  • Default menu item and a timeout

  • Higher-level / automatic / more flexible layout of objects

  • Image formats other than BMP

  • Use of ANSI sequences to control a serial terminal

  • Colour selection

  • Better support for handling lots of settings, e.g. with multiple menus and radio/option widgets

  • Mouse support

  • Integrate Nuklear, NxWidgets or some other library for a richer UI

  • Optimise rendering by only updating the display with changes since last render

  • Use expo to replace the existing menu implementation

  • Add a Kconfig option to drop the names to save code / data space

  • Add a Kconfig option to disable vidconsole support to save code / data space

  • Support both graphical and text menus at the same time on different devices

  • Implement proper measurement of object bounding boxes, to permit more exact layout. This would tidy up the layout when Truetype is not used

  • Support unicode

  • Support curses for proper serial-terminal menus