An EDK2 package that replaces the native text-based HII Form Browser UI with an LVGL-based graphical renderer.
Replace EDK2's DisplayEngineDxe with LvglDisplayEngineDxe, which produces
EFI_DISPLAY_ENGINE_PROTOCOL and renders HII forms using LVGL widgets.
SetupBrowserDxe (the IFR parser, expression evaluator, and config router)
continues to operate unchanged -- only the rendering layer is replaced.
BDS (F2/DEL) -> EFI_FORM_BROWSER2_PROTOCOL (SetupBrowserDxe)
|
| walks IFR, evaluates expressions, manages config
|
v
EFI_DISPLAY_ENGINE_PROTOCOL <- REPLACED
(LvglDisplayEngineDxe)
|
| FORM_DISPLAY_ENGINE_FORM -> LVGL widgets
|
v
LVGL -> EFI_GRAPHICS_OUTPUT_PROTOCOL -> pixels on screen
IFR parsing is not reimplemented. FormDisplay() receives a clean linked
list of FORM_DISPLAY_ENGINE_STATEMENT structs -- one per visible question --
and maps each to an LVGL widget.
| IFR Opcode | LVGL Widget |
|---|---|
| SUBTITLE | lv_label (styled) |
| TEXT | lv_label |
| CHECKBOX | lv_checkbox |
| NUMERIC | lv_textarea (digits only) |
| ONE_OF | lv_dropdown |
| STRING | lv_textarea |
| PASSWORD | lv_textarea (password mode) |
| REF (goto) | lv_btn |
| ACTION | lv_btn |
| ORDERED_LIST | vertical panel of rows with Up/Down buttons |
LvglPkg/
+--- Library/LvglLib/ LVGL UEFI port (display, mouse, keyboard)
| +--- LvglLib.c Init/deinit, tick, main loop
| +--- LvglUefiPort.c/.h libc shim (malloc/string/etc.) for the LVGL build
| +--- lv_port_indev.c Mouse (AbsolutePointer) + keyboard input
| +--- MouseCursorIcon.c Mouse cursor bitmap used by lv_port_indev.c
| +--- EscExitHandler.c ESC-to-exit confirmation popup handling
| `--- lvgl/ Upstream LVGL source (submodule)
+--- Library/LvglThemeLib/ Runtime UI-scale font/metric helpers
+--- Library/LvglUiConfigLib/ NVRAM/PCD UI configuration helpers
+--- LvglDisplayEngineDxe/ Display engine DXE driver (the main deliverable)
| +--- LvglDisplayEngineDxe.c Protocol installation, entry/unload
| +--- LvglFormRenderer.c FormDisplay() -> LVGL widget builder + event loop
| +--- LvglFormRenderer.h Renderer types and API
| +--- LvglAptioChrome.c/.h Aptio-style chrome (header/footer/nav bar)
| `--- AptioWallpaper.c Background image data
+--- LvglSetupDxe/ Graphical UI Configuration setup form
+--- Include/ Public headers (LvglLib.h, LvglTheme.h, ...)
+--- LvglPkg.dsc Package build description
+--- LvglPkg.dec Package declaration (PCDs)
`--- LICENSE MIT License
The GOP display flush and the AbsolutePointer/keyboard indev registration go through
LVGL's own built-in UEFI backend (lvgl/src/drivers/uefi/), enabled via
LV_USE_UEFI 1 / LV_USE_UEFI_INCLUDE "lv_uefi_edk2.h" in lv_conf.h. LvglLib.c
and lv_port_indev.c wire it up and add the wheel-scroll and press/hold keyboard
handling the built-in drivers don't provide on their own.
- Linux host (tested on Ubuntu 24.04). Build instructions assume bash.
- GCC 13.x (a working
gcc/g++toolchain). EDK2'sGCC5tag was retired in late 2024; this package builds with toolchain tagGCC. - NASM and iASL (
acpica-tools) -- required by EDK2'sBaseTools. - Python 3 -- used by the EDK2 build orchestrator.
- QEMU with
qemu-system-x86_64for testing OVMF builds. - An EDK2 checkout with submodules initialised:
git clone --recursive https://github.com/tianocore/edk2.git cd edk2 make -C BaseTools # build the EDK2 build tools
- LvglPkg checked out either as a sibling directory or as a submodule of the
edk2 tree. The LVGL source is itself a submodule under
LvglPkg/Library/LvglLib/lvgl, so--recursive(or a follow-upgit submodule update --init --recursive) is required.
Replacing the stock display engine in OVMF takes several coordinated edits.
In OvmfPkg/OvmfPkgX64.dsc, find and remove:
MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf
and replace it with:
LvglPkg/LvglDisplayEngineDxe/LvglDisplayEngineDxe.inf
LvglDisplayEngineDxe.inf (via LvglLib) requires the LvglThemeLib library
class for its runtime UI-scale font/metric helpers. Map it in
[LibraryClasses]:
LvglThemeLib|LvglPkg/Library/LvglThemeLib/LvglThemeLib.inf
In OvmfPkg/OvmfPkgX64.fdf, do the same swap:
# remove
INF MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf
# add
INF LvglPkg/LvglDisplayEngineDxe/LvglDisplayEngineDxe.inf
LvglSetupDxe publishes the Graphical UI Configuration form (UI scale,
centered aspect-ratio frame -- see Configuration below) and
is part of the standard integration alongside the display engine.
In OvmfPkg/OvmfPkgX64.dsc, add the driver to [Components] next to
LvglDisplayEngineDxe.inf:
LvglPkg/LvglSetupDxe/LvglSetupDxe.inf
and map its config library in [LibraryClasses]:
LvglUiConfigLib|LvglPkg/Library/LvglUiConfigLib/LvglUiConfigLib.inf
In OvmfPkg/OvmfPkgX64.fdf, add the driver to the DXE FV section next to
LvglDisplayEngineDxe.inf:
INF LvglPkg/LvglSetupDxe/LvglSetupDxe.inf
LVGL needs EFI_ABSOLUTE_POINTER_PROTOCOL. EDK2 ships two USB mouse drivers,
and they cannot coexist -- UsbMouseDxe advertises a higher driver-binding
Version than UsbMouseAbsolutePointerDxe, so the core picks it first and
locks UsbIo BY_DRIVER, blocking the AbsolutePointer driver. Remove the
former, add the latter.
In OvmfPkg/Include/Dsc/UsbComponents.dsc.inc and
OvmfPkg/OvmfPkgX64.fdf (under the DXE FV section):
# remove
MdeModulePkg/Bus/Usb/UsbMouseDxe/UsbMouseDxe.inf
# add
MdeModulePkg/Bus/Usb/UsbMouseAbsolutePointerDxe/UsbMouseAbsolutePointerDxe.inf
QEMU must use -device usb-mouse (Boot/Mouse class), not -device usb-tablet
-- neither EDK2 mouse driver binds to tablet's HID report descriptor.
Shortcut -- add-only integration
If you'd rather not edit the stock OVMF lines, you can simply add
LvglPkg/LvglDisplayEngineDxe/LvglDisplayEngineDxe.infto the DSC[Components]section and the FDF DXE FV section without removingMdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf. Both drivers will be built and dispatched, but only oneEFI_DISPLAY_ENGINE_PROTOCOLproducer wins -- whichever is installed last. In practiceLvglDisplayEngineDxereliably takes over because it dispatches after the stock module. The same shortcut applies to the mouse driver: leavingUsbMouseDxein place will block AbsolutePointer (driver-bindingVersionsort), so for mouse input the swap in step 4 is not optional.
Tell the EDK2 build where LvglPkg lives:
export PACKAGES_PATH=$HOME/workspace/edk2:$HOME/workspace/edk2/LvglPkg(adjust paths if your checkout is elsewhere).
LvglPkg separates theme values (rebuild to change) from platform defaults (DSC PCDs) and user settings (setup form, stored in NVRAM).
LvglSetupDxe publishes a Graphical UI Configuration form for:
- UI scale -- 1x / 1.5x / 2x via scaled fonts & layout metrics at native resolution (useful on HiDPI panels)
- Centered aspect-ratio frame -- optional letterboxed window instead of edge-to-edge chrome (useful on ultrawide displays)
Settings are stored in the non-volatile LvglUiScale variable. A reboot is
required for changes to take effect.
Wiring LvglSetupDxe and LvglUiConfigLib into a platform DSC/FDF is part of
the standard integration -- see step 3
above.
Override package defaults in the platform DSC [PcdsFixedAtBuild] section.
Declared in LvglPkg.dec:
| PCD | Purpose |
|---|---|
PcdLvglHelpPaneWidthPct |
Help pane width (% of content row) |
PcdLvglAptioHeaderTitle |
Header bar title (left) |
PcdLvglAptioHeaderVendor |
Header bar vendor string (center) |
PcdLvglCenteredFrameEnabled |
Enable centered frame by default |
PcdLvglCenteredFrameHeightPct |
Frame height (% of display height) |
PcdLvglCenteredFrameAspectNum / Den |
Frame aspect ratio (e.g. 16:9) |
Example:
[PcdsFixedAtBuild]
gLvglPkgTokenSpaceGuid.PcdLvglAptioHeaderTitle|"My Firmware Setup"
gLvglPkgTokenSpaceGuid.PcdLvglCenteredFrameEnabled|TRUEColors, fonts, padding, and widget styling used by the renderer. Edit and rebuild to restyle the UI. Chrome strings, help-pane width, and centered-frame defaults are controlled by PCDs and/or the setup form instead.
. edksetup.sh
build -p LvglPkg/LvglPkg.dsc -a X64 -t GCC -b RELEASEcd ~/workspace/edk2
. edksetup.sh
export PACKAGES_PATH=$HOME/workspace/edk2:$HOME/workspace/edk2/LvglPkg
build -a X64 -t GCC -b DEBUG -p OvmfPkg/OvmfPkgX64.dscOutputs:
Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd-- read-only firmware (code)Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd-- variable store (writable)
Stage the variable store and any user .efi files, then launch:
mkdir -p /tmp/efi_files
cp Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd /tmp/OVMF_VARS.fd
qemu-system-x86_64 \
-machine q35 \
-m 512M \
-smp 2 \
-drive if=pflash,format=raw,unit=0,readonly=on,file=Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd \
-drive if=pflash,format=raw,unit=1,file=/tmp/OVMF_VARS.fd \
-drive format=raw,file=fat:rw:/tmp/efi_files \
-device qemu-xhci,id=xhci \
-device usb-kbd,bus=xhci.0 \
-device usb-mouse,bus=xhci.0 \
-display gtk \
-serial stdioPress F2 or DEL at the OVMF splash to enter Setup -- HII forms render
through LVGL. Exit QEMU with Ctrl+A then X (when -serial stdio is used).
- Mouse cursor doesn't move / no response --
UsbMouseDxeis still in the firmware. CheckOvmfPkg/Include/Dsc/UsbComponents.dsc.incand the FDF; bothUsbMouseDxereferences must be replaced withUsbMouseAbsolutePointerDxe. Also verify QEMU launches with-device usb-mouse, notusb-tablet. - Black screen on entering Setup -- the stock
DisplayEngineDxewas not removed and is winning protocol-installation order, or LvglDisplayEngineDxe failed to load. Re-check both DSC and FDF -- the swap must happen in both files. buildfails with "package not found" --PACKAGES_PATHis not set, or doesn't contain the directory holdingLvglPkg/. Echo it and verify.fatal error: lvgl/lvgl.h: No such file or directory-- the LVGL submodule wasn't initialised. Rungit submodule update --init --recursiveinsideLvglPkg.- Stale build artefacts -- when toggling between DSC variants, wipe the
output directory:
rm -rf Build/OvmfX64.
Boot Maintenance Manager -- mixed widgets (list, dropdown, numeric field) with F9/F10 hotkeys in the footer:
Secure Boot Configuration -- checkbox + dropdown + grayed-out read-only row:
FormDisplay() works end-to-end. All major IFR opcodes map to LVGL widgets.
Keyboard navigation (UP/DOWN focus, ENTER to edit, ESC to exit), mouse input,
and string field commits are functional.
- LvglDisplayEngineDxe --
EFI_DISPLAY_ENGINE_PROTOCOLproducer -
FormDisplay()IFR -> LVGL widget builder - AbsolutePointer mouse input
- Mouse wheel
- Keyboard navigation (UP/DOWN focus, ESC exits, ENTER toggles editing)
-
EFI_IFR_ORDERED_LIST_OPrenderer with reorder commit - String field value commit (
HiiSetString+ pool buffer) - Aptio-style chrome (header/footer/nav/help pane)
- Function-key hotkeys (F9 Load Defaults, F10 Save, driver-registered hotkeys)
- Theme/styling pass (fonts, colors, readability)
- UI scaling (1x / 1.5x / 2x) via scaled fonts & layout metrics (LvglThemeLib)
- Graphical UI Configuration setup form (
LvglSetupDxe) - Opt-in centered aspect-ratio frame with letterbox backdrop
- Platform PCDs for chrome strings and layout defaults
- Front-page banner label refresh from HII
- Device model shown in the subtitle bar
- HII confirm/popup dialogs (
EFI_HII_POPUP_PROTOCOL) - On-screen keyboard (shown for text entry when a pointer is present)
-
grayoutif/ disabled control rendering
- VS2022 / AARCH64-GCC / Clang toolchain support
- Source cleanup (remove unused entries from
.inf)
Originally forked from YangGangUEFI/LvglPkg, which provides the LVGL-to-UEFI port (GOP display driver, input handling). This repository is now maintained independently. The display engine replacement and HII integration is original work by the current maintainer.
The original LVGL UEFI port code remains under its original copyright; see LICENSE.
MIT License. See LICENSE.


