Skip to content

LVGL 9.2 port (draft — tcan v2.0 migration)#206

Draft
mLupine wants to merge 3 commits intolvgl:masterfrom
mLupine:mLupine-lvgl-9
Draft

LVGL 9.2 port (draft — tcan v2.0 migration)#206
mLupine wants to merge 3 commits intolvgl:masterfrom
mLupine:mLupine-lvgl-9

Conversation

@mLupine
Copy link
Copy Markdown

@mLupine mLupine commented Apr 13, 2026

LVGL 9.2 port

This is a draft PR porting lv_binding_rust from LVGL 8.x to LVGL 9.2.x, opened as a conversation-starter rather than a merge-ready submission.

Motivation

I’m rewriting an ESP-IDF v6 firmware stack (originally Arduino-era C++) in Rust, and need LVGL 9.2 Rust bindings because esp_lvgl_port 2.3 (shipping with IDF v6) targets LVGL 9.x exclusively.

Upstream main targets LVGL 8.x. Issue #195 has been open since Feb 2025 asking for LVGL 9 support, with zero PRs in flight. This PR establishes a minimum-viable 9.x surface so downstream projects can unblock; broader API restoration (embedded-graphics integration, lv_drivers SDL/Linux backends, the full widget catalog, the style builder) is explicit follow-up work.

Branch

Changes summary

Three commits (full messages on the branch):

  1. be56b12 — vendor LVGL 9.2.2 + lvgl-sys bindgen against 9.x

    • Replace the LVGL 8.3.7 git submodule (vendor/lvgl, vendor/lv_drivers) with a vendored drop of LVGL v9.2.2 under lvgl-sys/vendor/lvgl-9.2/.
    • Add lvgl-sys/vendor/include-9.2/lv_conf.h (template-derived, LV_USE_SNAPSHOT=1).
    • Rewrite lvgl-sys/build.rs for the LVGL 9 source layout (cc compiles every .c under vendor/lvgl-9.2/src; bindgen runs against shims/lvgl_sys.h with allowlists lv_.* / LV_.* / _LV_COLOR_.*).
    • Update the LVGL-color shim for LVGL 9's native-RGB888 lv_color_t ({blue,green,red} field accessors; _LV_COLOR_GET_A returns 0xFF since alpha now lives in lv_color32_t).
    • Switch the lvgl-sys sanity test from lv_disp_get_hor_res (LVGL 8) to lv_display_get_horizontal_resolution (LVGL 9).
    • Bump both crate versions to 0.6.2-mLupine-lvgl-9.
  2. 4861b1b — port lvgl crate safe wrapper to LVGL 9.2 (minimum-viable)

    • Replace the 4083-LOC LVGL 8 lvgl/src/ tree with a slim hand-authored 9.x surface scoped to what downstream lupin-display needs:
      • display.rsDisplay::create + Display::from_raw for interop with esp_lvgl_port::lvgl_port_add_disp returns; LVGL-9 byte-buffer flush-callback adapter.
      • color.rsColor + ColorFormat enum mapping lv_color_format_t.
      • obj.rs, screen.rs — thin handles.
      • image_dsc.rs — alias over lv_image_dsc_t (renamed from LVGL 8's lv_img_dsc_t).
      • widgets/{label,image/mod}.rs.
      • snapshot.rslv_snapshot_take + lv_draw_buf_destroy (for RGB565 capture over UART).
    • build.rs becomes a no-op — LVGL 8 lvgl-codegen widget pipeline is unused in this PR (regenerating against LVGL 9 widget signatures is separate follow-up work).
    • Drop embedded-graphics / cstr_core / bitflags / paste / ctor and the LVGL 8 example matrix from Cargo.toml.
  3. 2ea8ccd — teach lvgl-sys/build.rs to find xtensa newlib headers

    • bindgen invokes a separate clang with -target xtensa-esp32s3-espidf that fails on <inttypes.h> because no sysroot is supplied; resolved by shelling out to xtensa-esp-elf-gcc -print-sysroot at build time and adding -I<sysroot>/include to bindgen's clang args (silently skipped when the xtensa toolchain isn't on PATH).

Verification

  • Host build: cargo build -p lvgl --target aarch64-apple-darwin → Finished.
  • Cross-compile: cargo +esp build -p lvgl --target xtensa-esp32s3-espidf on ESP-IDF v6 → Finished (one harmless improper_ctypes warning on a varargs va_list field).

Status

This PR is draft pending (any of these are welcome as conversation points, not blockers):

  • API-shape review from lv_binding_rust maintainers — I'd prefer to converge on upstream's preferred shape for the Display::from_raw_display / flush-callback / color-format-awareness seams rather than upstream ad-hoc choices.
  • Widget catalog restoration — the slim surface covers only what my project exercises today (label + image + snapshot + base Display). Bar, arc, slider, table, meter, keyboard, the style builder, and the embedded_graphics + lv_drivers integrations are deliberately not included.
  • Test-suite port — LVGL 8 tests hard-code layouts that no longer exist under LVGL 9.
  • input_device family — LVGL 9 removed lv_indev_drv_t; the input_device module is not ported.

Happy to split this into smaller PRs if that's easier to review (vendor-drop + lvgl-sys as one, safe wrapper as another, build.rs xtensa fix as a third).

Un-forking plan

Once this (or an equivalent) upstream PR merges and a released lvgl = "…" crate tracks LVGL 9.2, downstream tcan-rust reverts its .deps/lv_binding_rust submodule and consumes the crate directly.


Summary by cubic

Ports the Rust bindings to LVGL 9.2.2 and ships a minimal lvgl 9.x API (display, color, label, image, snapshot) to unblock ESP‑IDF v6 / esp_lvgl_port users.

  • New Features

    • Vendored LVGL 9.2.2 under lvgl-sys/vendor/lvgl-9.2/; updated lvgl-sys build (compile new src tree, bindgen allowlists) and color shims; tests use lv_display_get_*_resolution.
    • Minimal lvgl 9 API: Display::{create, from_raw}, byte-buffer flush adapter, Color/ColorFormat mapped to lv_color_format_t, widgets Label and Image, and Snapshot; dropped 8.x codegen, examples, and deps.
    • Cross-compile QoL: auto-detect xtensa newlib sysroot for bindgen; added inner .cargo/config.toml so local builds default to host.
  • Migration

    • Flush callbacks now receive raw bytes; interpret via the per-display color format. lv_color_t is RGB888 (BGR fields); alpha lives in lv_color32_t.
    • Use lv_display_* APIs (e.g., lv_display_get_horizontal_resolution). The 8.x lv_indev_drv_t path is not ported.
    • Not included in this PR: embedded-graphics, lv_drivers backends, and the full widget/style builder surface. Only Label, Image, and Snapshot are available today.

Written for commit 2ea8ccd. Summary will update on new commits.

mLupine added 3 commits April 13, 2026 12:55
Replace the LVGL 8.3.7 git submodule (lvgl-sys/vendor/lvgl) with a
vendored drop of the LVGL v9.2.2 release tarball at
lvgl-sys/vendor/lvgl-9.2/ (SHA256 129b4e00...). Add a vendored
lv_conf.h derived from upstream's 9.2 template with LV_USE_SNAPSHOT=1
(needed for DISP-03 pixel parity capture in Plan 04-09).

Rewrite lvgl-sys/build.rs for the LVGL 9 layout: cc compiles every .c
under vendor/lvgl-9.2/src + the lvgl_sys color shim; bindgen runs
against shims/lvgl_sys.h with allowlists for lv_.* / LV_.*. The legacy
lv_drivers (SDL/Linux backends) and the rust_timer/drivers feature
flags are kept as no-ops -- ESP-IDF v6 path uses esp_lvgl_port.

Update the lvgl_sys color shim for LVGL 9 lv_color_t (BGR888 native;
.red/.green/.blue fields). _LV_COLOR_GET_A returns 0xFF (alpha is in
lv_color32_t now). The lvgl-sys sanity test switches from LVGL 8's
lv_disp_get_hor_res to LVGL 9's lv_display_get_horizontal_resolution.

Bump lvgl-sys + lvgl crate versions to 0.6.2-mLupine-lvgl-9 to mark
the fork divergence (Cargo's prerelease-tag must be requested
explicitly, which is what we want from path consumers).

Add an inner .cargo/config.toml so 'cargo build' from inside the
fork defaults to host (instead of inheriting the parent's xtensa-
esp32s3-espidf target pin). Cross-compile target config is preserved.

Bindings verification:
  grep lv_display_t bindings.rs    -> 68 occurrences
  grep lv_image_dsc_t bindings.rs  -> 13 occurrences
  grep lv_disp_drv_t bindings.rs   -> 0  (LVGL 8 type fully absent)
  grep lv_snapshot_take bindings.rs -> 3
  lv_color_t struct = { blue: u8, green: u8, red: u8 }  (LVGL 9 BGR888)

cargo build -p lvgl-sys --target aarch64-apple-darwin -> Finished
Replace the LVGL 8 lvgl/src/ tree (4083 LOC across drivers, input_device,
embedded_graphics integration, full widget catalog, lv_core/style builder,
support.rs Color/LvError module, timer.rs, font module, allocator) with a
slim hand-authored LVGL 9 surface scoped to what lupin-display actually
needs per CONTEXT.md D-29..D-30:

  src/lib.rs        -- crate root with re-exports
  src/color.rs      -- Color (RGB888) + ColorFormat enum (RGB565, RGB888,
                       ARGB8888, I1, Native) mapped to lv_color_format_t
  src/display.rs    -- Display wrapping *mut lv_display_t with both
                       Display::create() AND Display::from_raw() (D-29-a:
                       wraps the pointer returned by lvgl_port_add_disp);
                       set_buffers/set_color_format/set_flush_cb following
                       LVGL 9's create-then-configure flow; LVGL 9 flush
                       cb signature uses *mut u8 (untyped byte buffer),
                       NOT *mut lv_color_t -- driver interprets via the
                       per-display color format
  src/obj.rs        -- thin Obj<*mut lv_obj_t> wrapper + screen_active()
                       (lv_screen_active() in 9.x; old lv_scr_act() macro
                       still works but the new spelling is preferred)
  src/screen.rs     -- non-owning Screen view obtained from
                       lv_display_get_screen_active
  src/image_dsc.rs  -- ImageDsc / ImageHeader type aliases over LVGL 9's
                       lv_image_dsc_t (renamed from LVGL 8's lv_img_dsc_t,
                       layout-compatible)
  src/widgets/mod.rs            -- pub mod label; pub mod image;
  src/widgets/label.rs          -- Label::{new,set_text,set_text_static}
  src/widgets/image/mod.rs      -- Image::{new,set_src_dsc,set_src_raw}
                                   using lv_image_create + lv_image_set_src
  src/snapshot.rs   -- Snapshot wrapper over lv_snapshot_take +
                       lv_draw_buf_destroy on Drop. Required for Plan
                       04-09 DISP-03 pixel-byte parity capture
                       (LV_USE_SNAPSHOT=1 is set in vendored lv_conf.h
                       per Task 1)

build.rs is now a no-op -- the LVGL 8 lvgl-codegen pipeline scraped 8.x
header signatures and emitted widgets/generated.rs; LVGL 9 widget
signatures don't match those patterns (lv_obj_t * lv_image_create vs.
lv_obj_t * lv_btn_create -- name conventions differ enough that
re-targeting codegen needs work that's tracked as upstream-PR follow-up
rather than blocking this plan). The widget surface lupin-display
needs (label + image) is small enough that hand-authored wrappers are
clearer.

Cargo.toml: drop embedded-graphics/cstr_core/bitflags/paste/ctor (all
LVGL-8-coupled), drop the example matrix (every example used the
lv_drivers SDL backend), drop nightly/lvgl_alloc/custom_allocator/
unsafe_no_autoinit/embedded_graphics features. Bump the default
feature set to [] (no embedded-graphics).

Add _LV_COLOR_.* allowlist to lvgl-sys/build.rs so bindgen exposes the
custom color-channel accessors from shims/lvgl_sys.h (these don't match
the lv_.* allowlist because they start with an underscore-prefix LV).

Acceptance regex (Plan 04-02 Task 2):
  grep -q lv_image_dsc_t lvgl/src/widgets/image/mod.rs   -> match
  grep -q lv_display_t   lvgl/src/display.rs              -> match
  ! grep -rq lv_disp_drv_t lvgl/src/                      -> 0 matches
  grep -rn lv_img_dsc_t lvgl/src/                         -> 0 matches
  grep -r snapshot_take lvgl/src/                         -> 7 matches
  grep -r from_raw      lvgl/src/display.rs               -> from_raw present

cargo build -p lvgl --target aarch64-apple-darwin -> Finished
When cross-compiling lvgl-sys for xtensa-esp32s3-espidf the C build
(cc-rs) succeeds because xtensa-esp-elf-gcc is on PATH and provides
its own includes implicitly. Bindgen then invokes a separate clang
with -target xtensa-esp32s3-espidf and that clang fails with
'inttypes.h file not found' because no sysroot is supplied.

Resolve by shelling out to xtensa-esp-elf-gcc -print-sysroot at build
time and adding -I<sysroot>/include to the bindgen clang args. Skipped
silently when the toolchain isn't on PATH (host builds).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant