Skip to content

Latest commit

 

History

History
181 lines (153 loc) · 9.18 KB

newcomers.md

File metadata and controls

181 lines (153 loc) · 9.18 KB

Newcomer's guide to the lvgl-python codebase

lvgl-python packages LVGL for CPython as the native lvgl extension distributed under the pydevices-lvgl project name. It is one endpoint of a shared binding family: it exposes the same intentional Python-shaped API as the MicroPython and CircuitPython targets, but it does not embed or run a MicroPython interpreter.

The most important boundary is ownership. This repository owns the CPython runtime layer, packaging, tests, and release policy. The sibling lvgl-bindings repository owns the generator and canonical API decisions. Generated binding files and the fs_driver.py helper are vendored here at one exact upstream commit; they are inputs to this project, not ordinary hand-edited source files.

A mental model

lvgl-bindings (generator and API policy)
        |
        | explicit sync at a recorded 40-character commit
        v
lvgl-python repository
  |-- generated/lvgl_python.c and lvgl.pyi      generated binding surface
  |-- src/lvpy_runtime.c                        CPython ownership, callbacks, locking
  |-- lvgl/                                     pinned upstream C submodule
  `-- setup.py                                  compiles one CPython extension
        |
        v
import lvgl as lv
  |-- generated wrappers convert Python arguments and results
  |-- runtime wrappers manage objects, structs, GIL, and callback lifetime
  `-- LVGL C core owns widgets, displays, rendering, and event dispatch

Most users install a prebuilt pydevices-lvgl wheel from TestPyPI, then use import lvgl as lv; they do not clone this repository, initialize its submodule, or compile LVGL. The build tree exists for binding/runtime contributors and release automation, not as the normal application setup.

pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pydevices-lvgl

An application may provide its own display backend. In the PyDevices convenience path it imports display_driver, pydevices' adapter around appdev.App and board_config. It ships in the pydevices wheel, outside this one, and is not required for a standalone backend.

Repository map

Path Purpose
generated/lvgl_python.c Vendored generated Python-to-LVGL wrappers, types, constants, and callback entry points.
generated/lvgl.pyi Type stubs installed beside the extension wheel.
src/lvpy_runtime.c, src/lvpy_runtime.h Handwritten CPython-specific implementation: object/struct wrappers, conversion helpers, locking, GIL handling, and callback ownership.
lvgl/ Pinned upstream LVGL C git submodule compiled into the extension.
lv_conf.h LVGL build configuration for the CPython target.
setup.py Defines the lvgl extension and compiles runtime, generated bindings, and all LVGL C sources.
fs_driver.py Synced LVGL filesystem helper; change its canonical copy in lvgl-bindings.
LVGL_BINDINGS_COMMIT Exact 40-character source commit used for the vendored binding inputs.
tests/ Native-extension lifecycle and wheel-installation contracts.
docs/building.md Authoritative local build, sync, Android, and Pyodide instructions.
docs/publishing.md Release safety model and platform-wheel policy.
scripts/sync_from_lvgl_bindings.sh Explicit upstream sync tool; it records the resolved source and updates the submodule pin.

Follow a normal PyDevices application

The usual PyDevices entry point imports display_driver before lvgl. That adapter selects the active board configuration and wires display flush, input, and LVGL event-loop coordination; the application then creates widgets.

import display_driver  # wires LVGL display, input, and event loop
import lvgl as lv

scr = lv.screen_active()
label = lv.label(scr)
label.set_text("Hello from PyDevices LVGL!")
label.center()
  1. Installing the supported wheel provides the binary module named lvgl; installing pydevices provides the display_driver helper.
  2. Importing display_driver first establishes the PyDevices display/input integration. It owns LVGL ticking and refresh coordination for this path.
  3. Importing lvgl exposes the generated module surface and runtime support types: LVGL-style enum namespaces, functions, object constructors, and struct methods rather than a separate Python UI model.
  4. Widget constructors and methods enter generated wrappers. They validate and convert Python values, take the runtime's LVGL lock where needed, and call the corresponding LVGL C API.

For a custom backend without PyDevices board configuration, use the raw LVGL setup in the root README instead of adapting this convenience-path example.

The public API is generated, but the lifecycle, ownership, and concurrency behavior in that path are CPython runtime work. Those concerns belong in src/lvpy_runtime.*, not in an ad hoc edit to the generated file.

Boundaries and invariants

  • Do not manually edit generated/lvgl_python.c, generated/lvgl.pyi, fs_driver.py, or lv_conf.h. Change the generator or canonical helper in lvgl-bindings, regenerate there, then use the explicit sync workflow here.
  • LVGL_BINDINGS_COMMIT is a reproducible-source record, not a loose version hint. A sync accepts an immutable full commit SHA or release tag and records the resolved 40-character commit.
  • A callback registered with add_event_cb is owned by its registration, not by the widget's Python wrapper: mp_lv_callback stores it in a dict that LVGL's event descriptor points at, and the runtime drops that dict's one reference when the event is removed or the object deleted. The drop is deferred until no LVGL call or Python callback is in flight, because LVGL sends LV_EVENT_DELETE to the runtime's hook before the user's. Dropping the wrapper neither stops nor leaks the callback; tests/test_callback_lifetime.py pins each case.
  • A struct's callbacks (lv_anim_t, lv_timer_t) share one dict reference, owned by the struct: the generated setters pass the struct as containing_struct. A Python-owned struct releases it in its dealloc; a timer and each running copy of an animation release theirs through LVGL's ext_data free_cb (lvpy_timer_bind, lvpy_anim_bind), which needs LV_USE_EXT_DATA, on for the CPython build only. tests/test_struct_callback_lifetime.py pins each case.
  • Pair lv.init() and lv.deinit() in standalone programs. Tests cover this lifecycle, including callback cleanup during interpreter shutdown.
  • The binding intentionally omits unsafe C signatures such as unbounded pointer-to-pointer or variadic APIs unless conversion is reviewed. Do not restore one merely to make the C surface look complete.
  • The project shares naming and selected API policy with other runtimes, but it is a CPython extension. A feature's availability may be explicitly different on another target.
  • The extension does not choose a display, event loop, or board. Treat the PyDevices display_driver path as one integration, not as a requirement of import lvgl.

When you need the source tree

Only work on a source checkout when you are changing the generated binding, the CPython runtime, packaging, or the release process. In that case, building.md is the authoritative platform-specific build and test guide; it covers the LVGL submodule and editable native build. It is not needed to use a published wheel.

The focused test suite verifies the recorded bindings source, the installed lvgl.pyi stub, initialization/deinitialization, widget creation, and teardown callbacks at interpreter exit. Read publishing.md before changing sync, version, wheel-matrix, or TestPyPI release behavior: normal pushes do not publish a wheel.

Safe first contributions

Good first changes are a focused regression test for a CPython lifecycle or conversion bug, a clarification in the build documentation, or a fix to the handwritten runtime layer when the generated API already expresses the desired surface. Confirm which repository owns the change before editing:

If the change concerns… Start in…
API availability, generated wrappers, or synced Python helpers lvgl-bindings
CPython object ownership, GIL/lock behavior, wheel packaging, or CPython tests lvgl-python
A PyDevices application's screens, board configuration, or user interaction that consuming application/repository

After changing any native code, rebuild the editable extension and run the focused tests. If a proposed change moves generated files or the lvgl submodule, use the documented sync path rather than staging a hand-picked mixture of outputs.

Where to learn next

Read src/lvpy_runtime.h first, then follow one call from import lvgl into generated/lvgl_python.c and back through src/lvpy_runtime.c. tests/test_lvgl_init.py is short and shows the lifecycle contract the runtime has to keep.

For the user-facing surface, both setup paths are in the README. For how the binding is generated and which API decisions it makes, go to lvgl-bindings. Building from source is in building.md, and releases are in publishing.md.