Chapter 29: Graphical Boot Menu
This chapter builds a graphical boot menu application that combines the Graphics Output Protocol (GOP) for pixel-level rendering with console input for keyboard navigation. The menu enumerates boot options stored in UEFI variables, displays them in a styled, navigable list, and launches the user’s selection.
29.1 Architecture Overview
graph TD
A[Boot Menu Application] --> B[GOP Framebuffer]
A --> C[Simple Text Input Ex Protocol]
A --> D[Boot Option Variables]
A --> E[Boot Services LoadImage/StartImage]
B --> F[DrawRect / FillScreen]
B --> G[RenderText via Blt]
C --> H[ReadKeyStrokeEx]
D --> I["GetVariable(BootOrder)"]
D --> J["GetVariable(Boot####)"]
E --> K[Launch Selected .efi]
sequenceDiagram
participant User
participant Menu as Boot Menu App
participant GOP as GOP Protocol
participant RT as Runtime Services
participant BS as Boot Services
Menu->>GOP: QueryMode / SetMode
Menu->>RT: GetVariable("BootOrder")
loop For each Boot####
Menu->>RT: GetVariable("Boot0000".."BootFFFF")
end
Menu->>GOP: Draw background, borders, items
loop User navigates
User->>Menu: Arrow key / Enter
Menu->>GOP: Update highlight
end
User->>Menu: Press Enter
Menu->>BS: LoadImage + StartImage
29.2 Boot Option Variables
UEFI stores boot options in NVRAM variables under the EFI_GLOBAL_VARIABLE GUID:
| Variable | Type | Description |
|---|---|---|
BootOrder |
UINT16[] |
Ordered list of boot option numbers |
Boot0000..BootFFFF |
EFI_LOAD_OPTION |
Individual boot option descriptors |
The EFI_LOAD_OPTION structure is:
typedef struct {
UINT32 Attributes; // LOAD_OPTION_ACTIVE, etc.
UINT16 FilePathListLength; // Bytes in FilePathList
// CHAR16 Description[]; // Null-terminated display name
// EFI_DEVICE_PATH FilePathList; // Device path to boot target
// UINT8 OptionalData[]; // Vendor-specific data
} EFI_LOAD_OPTION;
The fields after FilePathListLength are variable-length and packed sequentially.
29.3 Enumerating Boot Options
/**
Read BootOrder variable and populate the boot option list.
@param[out] Options Array of BOOT_MENU_ENTRY structures.
@param[out] Count Number of entries populated.
@retval EFI_SUCCESS Boot options read successfully.
**/
STATIC
EFI_STATUS
EnumerateBootOptions (
OUT BOOT_MENU_ENTRY **Options,
OUT UINTN *Count
)
{
EFI_STATUS Status;
UINT16 *BootOrder;
UINTN BootOrderSize;
UINTN OptionCount;
UINTN Index;
BootOrder = NULL;
BootOrderSize = 0;
//
// Read BootOrder variable
//
Status = gRT->GetVariable (
L"BootOrder",
&gEfiGlobalVariableGuid,
NULL,
&BootOrderSize,
NULL
);
if (Status != EFI_BUFFER_TOO_SMALL) {
return EFI_NOT_FOUND;
}
BootOrder = AllocatePool (BootOrderSize);
if (BootOrder == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gRT->GetVariable (
L"BootOrder",
&gEfiGlobalVariableGuid,
NULL,
&BootOrderSize,
BootOrder
);
if (EFI_ERROR (Status)) {
FreePool (BootOrder);
return Status;
}
OptionCount = BootOrderSize / sizeof (UINT16);
*Options = AllocateZeroPool (OptionCount * sizeof (BOOT_MENU_ENTRY));
if (*Options == NULL) {
FreePool (BootOrder);
return EFI_OUT_OF_RESOURCES;
}
*Count = 0;
for (Index = 0; Index < OptionCount; Index++) {
CHAR16 VariableName[16];
UINT8 *OptionData;
UINTN OptionSize;
UnicodeSPrint (VariableName, sizeof (VariableName),
L"Boot%04X", BootOrder[Index]);
OptionSize = 0;
Status = gRT->GetVariable (
VariableName,
&gEfiGlobalVariableGuid,
NULL,
&OptionSize,
NULL
);
if (Status != EFI_BUFFER_TOO_SMALL) {
continue;
}
OptionData = AllocatePool (OptionSize);
if (OptionData == NULL) {
continue;
}
Status = gRT->GetVariable (
VariableName,
&gEfiGlobalVariableGuid,
NULL,
&OptionSize,
OptionData
);
if (EFI_ERROR (Status)) {
FreePool (OptionData);
continue;
}
//
// Parse EFI_LOAD_OPTION
//
UINT32 Attributes = *(UINT32 *)OptionData;
UINT16 FilePathListLen = *(UINT16 *)(OptionData + 4);
CHAR16 *Description = (CHAR16 *)(OptionData + 6);
//
// Skip inactive options
//
if ((Attributes & LOAD_OPTION_ACTIVE) == 0) {
FreePool (OptionData);
continue;
}
//
// Populate menu entry
//
BOOT_MENU_ENTRY *Entry = &((*Options)[*Count]);
Entry->BootNumber = BootOrder[Index];
StrnCpyS (Entry->Description, BOOT_DESC_MAX, Description,
BOOT_DESC_MAX - 1);
UINTN DescBytes = StrSize (Description);
Entry->DevicePath = DuplicateDevicePath (
(EFI_DEVICE_PATH_PROTOCOL *)(OptionData + 6 + DescBytes)
);
Entry->RawData = OptionData; // Caller frees
Entry->RawDataSize = OptionSize;
(*Count)++;
}
FreePool (BootOrder);
return EFI_SUCCESS;
}
29.4 GOP Drawing Primitives
The following helper functions perform basic framebuffer drawing through the EFI_GRAPHICS_OUTPUT_PROTOCOL:
#define COLOR_BG {0x20, 0x20, 0x30, 0x00} // Dark blue-gray
#define COLOR_BORDER {0x60, 0x80, 0xC0, 0x00} // Steel blue
#define COLOR_ITEM_BG {0x30, 0x30, 0x40, 0x00} // Slightly lighter
#define COLOR_HIGHLIGHT {0x40, 0x60, 0xA0, 0x00} // Bright selection
#define COLOR_TEXT {0xFF, 0xFF, 0xFF, 0x00} // White
/**
Fill a rectangle on the framebuffer.
@param[in] Gop Graphics Output Protocol instance.
@param[in] Color Fill color.
@param[in] X Left coordinate.
@param[in] Y Top coordinate.
@param[in] Width Rectangle width in pixels.
@param[in] Height Rectangle height in pixels.
**/
STATIC
VOID
FillRect (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color,
IN UINTN X,
IN UINTN Y,
IN UINTN Width,
IN UINTN Height
)
{
//
// Create a single-pixel buffer and Blt with EfiBltVideoFill
//
Gop->Blt (
Gop,
&Color,
EfiBltVideoFill,
0, 0, // Source (ignored for fill)
X, Y, // Destination
Width, Height,
0 // Delta (ignored for fill)
);
}
/**
Draw a bordered box.
@param[in] Gop GOP instance.
@param[in] X, Y Top-left corner.
@param[in] W, H Outer dimensions.
@param[in] BorderWidth Border thickness in pixels.
@param[in] BorderColor Color for the border.
@param[in] FillColor Color for the interior.
**/
STATIC
VOID
DrawBox (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINTN X,
IN UINTN Y,
IN UINTN W,
IN UINTN H,
IN UINTN BorderWidth,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL BorderColor,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL FillColor
)
{
// Border (draw as four rects to avoid overdraw)
FillRect (Gop, BorderColor, X, Y, W, BorderWidth); // Top
FillRect (Gop, BorderColor, X, Y + H - BorderWidth, W, BorderWidth); // Bottom
FillRect (Gop, BorderColor, X, Y, BorderWidth, H); // Left
FillRect (Gop, BorderColor, X + W - BorderWidth, Y, BorderWidth, H); // Right
// Interior fill
FillRect (Gop, FillColor,
X + BorderWidth, Y + BorderWidth,
W - 2 * BorderWidth, H - 2 * BorderWidth);
}
/**
Fill the entire screen with a solid color.
**/
STATIC
VOID
ClearScreen (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color
)
{
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info = Gop->Mode->Info;
FillRect (Gop, Color, 0, 0,
Info->HorizontalResolution,
Info->VerticalResolution);
}
29.5 Text Rendering
UEFI does not provide a pixel-level font API through GOP alone. The simplest approach is to use EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL alongside GOP. The text console is overlaid on the framebuffer:
/**
Position the text cursor and print a string using the console.
@param[in] Con SimpleTextOutput protocol.
@param[in] Col Column (character position).
@param[in] Row Row (character position).
@param[in] Attr Text attribute (foreground | background).
@param[in] Fmt Format string.
@param[in] ... Format arguments.
**/
STATIC
VOID
EFIAPI
PrintAt (
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *Con,
IN UINTN Col,
IN UINTN Row,
IN UINTN Attr,
IN CONST CHAR16 *Fmt,
...
)
{
VA_LIST Args;
CHAR16 Buffer[256];
Con->SetCursorPosition (Con, Col, Row);
Con->SetAttribute (Con, Attr);
VA_START (Args, Fmt);
UnicodeVSPrint (Buffer, sizeof (Buffer), Fmt, Args);
VA_END (Args);
Con->OutputString (Con, Buffer);
}
For fully graphical text rendering independent of the console, you would implement a bitmap font renderer that blits character glyphs into the GOP framebuffer. That approach is covered in the HII font protocol (EFI_HII_FONT_PROTOCOL).
29.6 Rendering the Menu
#define MENU_START_ROW 4
#define MENU_START_COL 4
#define MENU_ITEM_HEIGHT 1 // rows per item
/**
Draw the complete boot menu.
@param[in] Con Console output for text.
@param[in] Options Array of boot menu entries.
@param[in] Count Number of entries.
@param[in] Selected Currently highlighted index.
**/
STATIC
VOID
DrawMenu (
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *Con,
IN BOOT_MENU_ENTRY *Options,
IN UINTN Count,
IN UINTN Selected
)
{
UINTN Index;
UINTN Row;
UINTN Attr;
//
// Title bar
//
PrintAt (Con, MENU_START_COL, 1,
EFI_WHITE | EFI_BACKGROUND_BLUE,
L" Boot Menu - Use Arrow Keys, Enter to Select ");
//
// Separator
//
PrintAt (Con, MENU_START_COL, 3,
EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK,
L"----------------------------------------------------");
//
// Menu items
//
for (Index = 0; Index < Count; Index++) {
Row = MENU_START_ROW + Index * MENU_ITEM_HEIGHT;
if (Index == Selected) {
Attr = EFI_WHITE | EFI_BACKGROUND_CYAN;
} else {
Attr = EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK;
}
//
// Format: " [Boot####] Description "
//
PrintAt (Con, MENU_START_COL, Row, Attr,
L" [Boot%04X] %-40s",
Options[Index].BootNumber,
Options[Index].Description);
}
//
// Footer
//
Row = MENU_START_ROW + Count * MENU_ITEM_HEIGHT + 1;
PrintAt (Con, MENU_START_COL, Row,
EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK,
L"----------------------------------------------------");
PrintAt (Con, MENU_START_COL, Row + 1,
EFI_YELLOW | EFI_BACKGROUND_BLACK,
L" UP/DOWN: Navigate ENTER: Boot ESC: Reboot ");
}
29.7 Keyboard Navigation Loop
/**
Main input loop for the boot menu.
@param[in] Options Boot option list.
@param[in] Count Number of options.
@retval Index of selected option, or (UINTN)-1 for ESC/reboot.
**/
STATIC
UINTN
RunMenuLoop (
IN BOOT_MENU_ENTRY *Options,
IN UINTN Count
)
{
EFI_STATUS Status;
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *InputEx;
EFI_KEY_DATA KeyData;
UINTN Selected;
Selected = 0;
//
// Locate extended input protocol for key data
//
Status = gBS->LocateProtocol (
&gEfiSimpleTextInputExProtocolGuid,
NULL,
(VOID **)&InputEx
);
if (EFI_ERROR (Status)) {
//
// Fall back to simple text input
//
InputEx = NULL;
}
//
// Initial draw
//
DrawMenu (gST->ConOut, Options, Count, Selected);
while (TRUE) {
//
// Wait for a key event
//
UINTN EventIndex;
gBS->WaitForEvent (1, &gST->ConIn->WaitForKey, &EventIndex);
if (InputEx != NULL) {
Status = InputEx->ReadKeyStrokeEx (InputEx, &KeyData);
} else {
EFI_INPUT_KEY Key;
Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
KeyData.Key = Key;
}
if (EFI_ERROR (Status)) {
continue;
}
switch (KeyData.Key.ScanCode) {
case SCAN_UP:
if (Selected > 0) {
Selected--;
} else {
Selected = Count - 1; // Wrap around
}
DrawMenu (gST->ConOut, Options, Count, Selected);
break;
case SCAN_DOWN:
if (Selected < Count - 1) {
Selected++;
} else {
Selected = 0; // Wrap around
}
DrawMenu (gST->ConOut, Options, Count, Selected);
break;
case SCAN_ESC:
return (UINTN)-1;
case SCAN_NULL:
//
// Check UnicodeChar for Enter
//
if (KeyData.Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
return Selected;
}
break;
}
}
}
29.8 Launching a Boot Option
/**
Launch the selected boot option.
@param[in] Entry The boot menu entry to launch.
@retval EFI_SUCCESS Image started (may return on failure).
@retval EFI_NOT_FOUND Could not load image.
**/
STATIC
EFI_STATUS
LaunchBootOption (
IN BOOT_MENU_ENTRY *Entry
)
{
EFI_STATUS Status;
EFI_HANDLE ImageHandle;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
DevicePath = Entry->DevicePath;
if (DevicePath == NULL) {
Print (L"Error: no device path for Boot%04X\r\n", Entry->BootNumber);
return EFI_NOT_FOUND;
}
//
// Load the boot image
//
Status = gBS->LoadImage (
TRUE, // BootPolicy
gImageHandle, // Parent image
DevicePath,
NULL, // SourceBuffer
0, // SourceSize
&ImageHandle
);
if (EFI_ERROR (Status)) {
Print (L"Error: LoadImage failed for Boot%04X: %r\r\n",
Entry->BootNumber, Status);
return Status;
}
//
// Start the image
//
Status = gBS->StartImage (ImageHandle, NULL, NULL);
//
// If StartImage returns, the boot target exited or failed.
// We return to the menu.
//
Print (L"Boot%04X returned: %r\r\n", Entry->BootNumber, Status);
gBS->Stall (2000000); // 2 second pause
return Status;
}
29.9 Complete Main Entry Point
/** @file
BootMenu -- Graphical boot menu UEFI application.
Copyright (c) 2026, Your Name. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/DevicePathLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleTextInEx.h>
#include <Guid/GlobalVariable.h>
#define BOOT_DESC_MAX 80
#define LOAD_OPTION_ACTIVE 0x00000001
typedef struct {
UINT16 BootNumber;
CHAR16 Description[BOOT_DESC_MAX];
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
UINT8 *RawData;
UINTN RawDataSize;
} BOOT_MENU_ENTRY;
// (Include all functions from sections 29.3 through 29.8 above)
/**
Application entry point.
**/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
BOOT_MENU_ENTRY *Options;
UINTN OptionCount;
UINTN Selection;
//
// Locate GOP (optional -- we can work with console-only too)
//
Status = gBS->LocateProtocol (
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)&Gop
);
if (!EFI_ERROR (Status)) {
//
// Clear screen with background color
//
EFI_GRAPHICS_OUTPUT_BLT_PIXEL BgColor = COLOR_BG;
ClearScreen (Gop, BgColor);
}
//
// Disable cursor for cleaner menu rendering
//
gST->ConOut->EnableCursor (gST->ConOut, FALSE);
gST->ConOut->ClearScreen (gST->ConOut);
//
// Enumerate boot options from UEFI variables
//
Status = EnumerateBootOptions (&Options, &OptionCount);
if (EFI_ERROR (Status) || OptionCount == 0) {
Print (L"No boot options found. Dropping to Shell.\r\n");
return EFI_NOT_FOUND;
}
//
// Run the interactive menu
//
while (TRUE) {
Selection = RunMenuLoop (Options, OptionCount);
if (Selection == (UINTN)-1) {
//
// User pressed ESC -- reset system
//
gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL);
// Should not return
CpuDeadLoop ();
}
//
// Attempt to boot the selected option
//
Status = LaunchBootOption (&Options[Selection]);
//
// If boot returns, redraw menu
//
gST->ConOut->ClearScreen (gST->ConOut);
if (!EFI_ERROR (Status) && Gop != NULL) {
EFI_GRAPHICS_OUTPUT_BLT_PIXEL BgColor = COLOR_BG;
ClearScreen (Gop, BgColor);
}
}
// Unreachable
return EFI_SUCCESS;
}
29.10 INF File
[Defines]
INF_VERSION = 0x00010017
BASE_NAME = BootMenu
FILE_GUID = B1B2C3D4-5678-9ABC-DEF0-123456789ABC
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
BootMenu.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
UefiBootServicesTableLib
UefiRuntimeServicesTableLib
BaseMemoryLib
MemoryAllocationLib
PrintLib
DevicePathLib
[Protocols]
gEfiGraphicsOutputProtocolGuid
gEfiSimpleTextInputExProtocolGuid
[Guids]
gEfiGlobalVariableGuid
29.11 Custom Styling and Branding
You can extend the menu with vendor branding:
/**
Draw a logo bitmap at the top of the screen.
In production firmware this would use EFI_HII_IMAGE_PROTOCOL or
load a BMP from FV. For simplicity, we draw a colored banner.
**/
STATIC
VOID
DrawBanner (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop
)
{
EFI_GRAPHICS_OUTPUT_BLT_PIXEL BannerColor = {0x00, 0x50, 0xA0, 0x00};
UINTN Width = Gop->Mode->Info->HorizontalResolution;
// Full-width banner, 80 pixels tall
FillRect (Gop, BannerColor, 0, 0, Width, 80);
}
For loading an actual BMP image:
- Include the BMP in the firmware volume (FV) as a
RAWfile. - Use
gBS->LocateProtocolto findEFI_HII_IMAGE_PROTOCOL. - Call
NewImage/DrawImageto render it.
Alternatively, decode the BMP manually:
// Simplified BMP header check and pixel extraction
typedef struct {
CHAR8 Signature[2]; // 'B', 'M'
UINT32 FileSize;
UINT32 Reserved;
UINT32 DataOffset;
UINT32 HeaderSize;
INT32 Width;
INT32 Height;
UINT16 Planes;
UINT16 BitsPerPixel;
// ... compression, etc.
} BMP_HEADER;
29.12 Testing in QEMU
# Build the boot menu application
stuart_build -c Platforms/YourPlatform/PlatformBuild.py
# Create a test disk image with multiple boot options
mkdir -p /tmp/boot-test/EFI/BOOT
cp Build/BootMenuPkg/DEBUG_GCC5/X64/BootMenu.efi \
/tmp/boot-test/EFI/BOOT/BOOTX64.EFI
# Launch QEMU with OVMF
qemu-system-x86_64 \
-bios OVMF.fd \
-drive file=fat:rw:/tmp/boot-test,format=raw,media=disk \
-device virtio-gpu-pci \
-display gtk \
-m 256
To test with actual boot options you need to set UEFI variables. You can use the UEFI Shell bcfg command:
Shell> bcfg boot add 0 fs0:\EFI\BOOT\Shell.efi "UEFI Shell"
Shell> bcfg boot add 1 fs0:\EFI\BOOT\grubx64.efi "GRUB Bootloader"
Then reboot into the boot menu to see those entries listed.
29.13 Advanced Topics
Timeout and Auto-Boot
/**
If no key is pressed within TimeoutSeconds, auto-boot the first entry.
**/
STATIC
UINTN
RunMenuLoopWithTimeout (
IN BOOT_MENU_ENTRY *Options,
IN UINTN Count,
IN UINTN TimeoutSeconds
)
{
UINTN Remaining = TimeoutSeconds;
UINTN Selected = 0;
DrawMenu (gST->ConOut, Options, Count, Selected);
while (Remaining > 0) {
PrintAt (gST->ConOut, 4, MENU_START_ROW + Count + 3,
EFI_YELLOW | EFI_BACKGROUND_BLACK,
L" Auto-boot in %2d seconds... ", Remaining);
//
// Wait 1 second or until key press
//
EFI_EVENT TimerEvent;
gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimerEvent);
gBS->SetTimer (TimerEvent, TimerRelative, 10000000); // 1 second
UINTN EventIndex;
EFI_EVENT WaitEvents[2];
WaitEvents[0] = gST->ConIn->WaitForKey;
WaitEvents[1] = TimerEvent;
gBS->WaitForEvent (2, WaitEvents, &EventIndex);
gBS->CloseEvent (TimerEvent);
if (EventIndex == 0) {
// Key pressed -- enter interactive mode
return RunMenuLoop (Options, Count);
}
Remaining--;
}
return 0; // Auto-boot first entry
}
Mouse/Touch Support
For platforms with EFI_SIMPLE_POINTER_PROTOCOL or EFI_ABSOLUTE_POINTER_PROTOCOL, you can add mouse/touch input by polling the pointer state and mapping coordinates to menu item positions.
29.14 Key Takeaways
- GOP provides pixel-level framebuffer access through
Bltoperations –EfiBltVideoFillis the workhorse for solid rectangles. - The console text output (
SimpleTextOutput) can be used alongside GOP for text rendering without implementing a bitmap font. - Boot options are stored as
BootOrder+Boot####UEFI variables; parse the packedEFI_LOAD_OPTIONstructure to extract descriptions and device paths. LoadImage+StartImagelaunches a boot target; if it returns, you can fall back to the menu.- Keyboard navigation uses
WaitForEventon the console input event, thenReadKeyStrokeorReadKeyStrokeEx. - Timeouts use
CreateEvent/SetTimerwithTimerRelativeto implement auto-boot countdowns.
Complete source code: The full working example for this chapter is available at
examples/UefiMuGuidePkg/BootMenu/.
Summary
This project demonstrates how to build an interactive, graphical boot menu that ties together display output, user input, UEFI variable storage, and image loading. The patterns here form the foundation for production boot managers and pre-boot UI applications found in modern firmware.