-
Notifications
You must be signed in to change notification settings - Fork 197
Feat/wayland driver binding #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,6 +178,57 @@ Driver code is under `/driver` directory. | |
| Drivers can also be implemented in pure MicroPython, by providing callbacks (`disp_drv.flush_cb`, `indev_drv.read_cb` etc.) | ||
| Currently the supported ILI9341, FT6X36 and XPT2046 are pure micropython drivers. | ||
|
|
||
| ### Wayland driver | ||
|
|
||
| On Linux, LVGL also ships a native Wayland driver (`lvgl/src/drivers/wayland`) as | ||
| an alternative to SDL. Usage mirrors the SDL example above: | ||
|
|
||
| ```python | ||
| import lvgl as lv | ||
| lv.init() | ||
|
|
||
| from lv_utils import event_loop | ||
|
|
||
| WIDTH = 480 | ||
| HEIGHT = 320 | ||
|
|
||
| event_loop = event_loop() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The Wayland example creates a default event_loop (which calls lv.task_handler = lv_timer_handler() periodically) AND also calls lv.wayland_timer_handler() in the manual while loop. The event_loop source explicitly says lv.wayland_timer_handler() must be used instead of the plain handler since it wraps the same inner call with Wayland event processing. Remove the event_loop line — the manual while loop with lv.wayland_timer_handler() is the complete replacement. Prompt for AI agents |
||
| disp_drv = lv.wayland_window_create_simple(WIDTH, HEIGHT, "MicroPython-LVGL") | ||
| pointer = lv.wayland_pointer_create() | ||
| keyboard = lv.wayland_keyboard_create() | ||
|
|
||
| while lv.wayland_window_is_open(disp_drv): | ||
| lv.wayland_timer_handler() | ||
| ``` | ||
|
|
||
| `lv.wayland_window_create_simple` takes only `hor_res, ver_res, title` (no | ||
| close-callback - `gen_mpy.py` can't bind `lv_wayland_window_create()`'s C | ||
| callback param, see [gen/lv_mpy_drivers.h](gen/lv_mpy_drivers.h)). Poll | ||
| `lv.wayland_window_is_open()` to detect the window closing instead. | ||
|
|
||
| **Why**: `lv_sdl_window_create()` can segfault deep in the NVIDIA GL/EGL | ||
| driver as soon as a real window is presented, on Wayland sessions with the | ||
| proprietary NVIDIA driver - a long-standing upstream issue | ||
| ([#46](https://github.com/lvgl/lv_binding_micropython/issues/46)), caused by | ||
| this binding's SDL driver running on MicroPython's own thread rather than a | ||
| dedicated one. LVGL's native Wayland driver avoids it entirely: it presents | ||
| via `wl_shm` and never touches GL/EGL. It was already upstream in LVGL, just | ||
| never wired into this binding's build or exposed to Python. | ||
|
|
||
| While wiring it up we also found no LVGL desktop driver (SDL included) has | ||
| been reachable from Python at all since | ||
| [3f32386](https://github.com/lvgl/lv_binding_micropython/commit/3f32386af5f3ee9ba37d9282b1926e39b74162d7) | ||
| (2026-05-02) switched `gen_mpy.py`'s input from `lvgl.h` to | ||
| `lvgl_private.h`, which doesn't pull in `src/drivers/lv_drivers.h` - a silent | ||
| regression, also breaking `tests/testdisplay.py`'s interactive mode. Fixed | ||
| the same way here: gen_mpy now parses | ||
| [gen/lv_mpy_drivers.h](gen/lv_mpy_drivers.h) instead, which includes both. | ||
|
|
||
| **Build requirements**: autodetected via `pkg-config` in `micropython.mk` | ||
| like SDL - left disabled if `wayland-client`/`wayland-cursor`/`xkbcommon`/ | ||
| `wayland-scanner`/`wayland-protocols` aren't found. `LV_COLOR_DEPTH` must | ||
| stay `32`; see the comment above its definition in `lv_conf.h`. | ||
|
|
||
| ### Where are the drivers? | ||
|
|
||
| LVGL C drivers and MicroPython drivers (either C or Python) are **separate and independent** from each other. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /** | ||
| * @file lv_mpy_drivers.c | ||
| * | ||
| * Implementation of the binding-friendly driver wrappers declared for | ||
| * gen_mpy.py in lv_mpy_drivers.h. See that file for why this is needed. | ||
| */ | ||
|
|
||
| #include "lvgl.h" | ||
|
|
||
| #if LV_USE_WAYLAND | ||
|
|
||
| lv_display_t * lv_wayland_window_create_simple(uint32_t hor_res, uint32_t ver_res, char * title) | ||
| { | ||
| return lv_wayland_window_create(hor_res, ver_res, title, NULL); | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /** | ||
| * @file lv_mpy_drivers.h | ||
| * | ||
| * gen_mpy.py builds its binding AST from a single preprocessed input file | ||
| * (see the LVGL_MPY rule in micropython.mk). lvgl_private.h alone only | ||
| * pulls in _private.h counterparts for libinput/evdev, so desktop window | ||
| * drivers (sdl/wayland/x11/...) never reach the parser and never get | ||
| * Python bindings, regardless of LV_USE_SDL/LV_USE_WAYLAND/etc. This | ||
| * wrapper adds the public driver headers to gen_mpy's input so any driver | ||
| * enabled in lv_conf.h is bound the same way widgets are. | ||
| */ | ||
|
|
||
| #include "lvgl_private.h" | ||
|
|
||
| #if LV_USE_WAYLAND | ||
| /* | ||
| * lv_wayland_window_create()'s close_cb parameter has no accompanying | ||
| * user_data-carrying struct argument (its first parameter is a plain | ||
| * uint32_t, not an lv_obj/lv_display, which is what gen_mpy's | ||
| * callback-binding heuristic requires to know where to stash the | ||
| * Python callable). gen_mpy silently emits invalid C | ||
| * (`hor_res->user_data`) for it instead of erroring, so hide the | ||
| * original declaration from the parser here and expose | ||
| * lv_wayland_window_create_simple() instead (implemented in | ||
| * lv_mpy_drivers.c), which has no callback parameter at all -- window | ||
| * close is already observable via lv_wayland_window_is_open(). | ||
| */ | ||
| #define lv_wayland_window_create(...) lv_wayland_window_create_native_unbound | ||
| #endif | ||
|
|
||
| #include "src/drivers/lv_drivers.h" | ||
|
|
||
| #if LV_USE_WAYLAND | ||
| #undef lv_wayland_window_create | ||
| lv_display_t * lv_wayland_window_create_simple(uint32_t hor_res, uint32_t ver_res, char * title); | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,31 @@ ifeq ($(UNAME_S),Darwin) | |
| CFLAGS_USERMOD:=$(filter-out -I/usr/local/include,$(CFLAGS_USERMOD)) | ||
| endif | ||
|
|
||
| WAYLAND_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags wayland-client wayland-cursor xkbcommon) | ||
| WAYLAND_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs wayland-client wayland-cursor xkbcommon) | ||
| WAYLAND_SCANNER := $(shell pkg-config --silence-errors --variable=wayland_scanner wayland-scanner) | ||
| ifeq ($(WAYLAND_SCANNER),) | ||
| WAYLAND_SCANNER := $(shell command -v wayland-scanner) | ||
| endif | ||
| WAYLAND_PROTOCOLS_DIR := $(shell pkg-config --silence-errors --variable=pkgdatadir wayland-protocols) | ||
| ifneq ($(and $(WAYLAND_LDFLAGS_USERMOD),$(WAYLAND_SCANNER),$(WAYLAND_PROTOCOLS_DIR)),) | ||
| # lv_wl_xdg_shell.c needs generated xdg-shell client protocol glue that | ||
| # isn't shipped by wayland-protocols as source. Generate it eagerly (at | ||
| # Makefile-parse time, like the pkg-config detection above) into the | ||
| # build dir so it exists before any compilation starts -- a build *rule* | ||
| # for this would race lv_wl_xdg_shell.c's compile on a parallel first build, | ||
| # since nothing else orders one before the other. | ||
| LVGL_WAYLAND_PROTOCOLS_DIR := $(BUILD)/lvgl/wayland_protocols | ||
| LVGL_WAYLAND_XDG_SHELL_XML := $(WAYLAND_PROTOCOLS_DIR)/stable/xdg-shell/xdg-shell.xml | ||
| $(shell mkdir -p $(LVGL_WAYLAND_PROTOCOLS_DIR)) | ||
| $(shell $(WAYLAND_SCANNER) client-header $(LVGL_WAYLAND_XDG_SHELL_XML) $(LVGL_WAYLAND_PROTOCOLS_DIR)/wayland_xdg_shell.h) | ||
| $(shell $(WAYLAND_SCANNER) private-code $(LVGL_WAYLAND_XDG_SHELL_XML) $(LVGL_WAYLAND_PROTOCOLS_DIR)/wayland_xdg_shell.c) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Incremental Wayland builds recompile generated protocol glue on every Prompt for AI agents |
||
|
|
||
| CFLAGS_USERMOD += $(WAYLAND_CFLAGS_USERMOD) -DMICROPY_WAYLAND=1 -I$(LVGL_WAYLAND_PROTOCOLS_DIR) -I$(USERMOD_DIR)/lvgl | ||
| LDFLAGS_USERMOD += $(WAYLAND_LDFLAGS_USERMOD) | ||
| SRC_USERMOD_LIB_C += $(LVGL_WAYLAND_PROTOCOLS_DIR)/wayland_xdg_shell.c | ||
| endif | ||
|
|
||
| RLOTTIE_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags rlottie) | ||
| RLOTTIE_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs rlottie) | ||
| ifneq ($(RLOTTIE_LDFLAGS_USERMOD),) | ||
|
|
@@ -76,16 +101,17 @@ CFLAGS_USERMOD += -DLV_CONF_PATH='"$(LV_CONF_PATH)"' -Wno-deprecated-declaration | |
| # CFLAGS DEBUG | ||
| $(info CFLAGS_USERMOD is $(CFLAGS_USERMOD)) | ||
|
|
||
| $(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py | ||
| $(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py $(LVGL_BINDING_DIR)/gen/lv_mpy_drivers.h | ||
| $(ECHO) "LVGL-GEN $@" | ||
| $(Q)mkdir -p $(dir $@) | ||
| $(Q)$(CPP) -DPYCPARSER -x c \ | ||
| -I $(LVGL_BINDING_DIR)/stubs/include/freetype2 \ | ||
| -I $(LVGL_BINDING_DIR)/pycparser/utils/fake_libc_include \ | ||
| -I $(LVGL_BINDING_DIR)/stubs/include \ | ||
| -I $(LVGL_DIR) \ | ||
| $(CFLAGS_USERMOD) \ | ||
| $(LVGL_DIR)/lvgl_private.h > $(LVGL_PP) | ||
| $(Q)$(PYTHON) $(LVGL_BINDING_DIR)/gen/gen_mpy.py -M lvgl -MP lv -MD $(LVGL_MPY_METADATA) -E $(LVGL_PP) $(LVGL_DIR)/lvgl.h > $@ | ||
| $(LVGL_BINDING_DIR)/gen/lv_mpy_drivers.h > $(LVGL_PP) | ||
| $(Q)$(PYTHON) $(LVGL_BINDING_DIR)/gen/gen_mpy.py -M lvgl -MP lv -MD $(LVGL_MPY_METADATA) -E $(LVGL_PP) $(LVGL_DIR)/lvgl.h $(LVGL_BINDING_DIR)/gen/lv_mpy_drivers.h > $@ | ||
|
|
||
| .PHONY: LVGL_MPY | ||
| LVGL_MPY: $(LVGL_MPY) | ||
|
|
@@ -96,6 +122,9 @@ CFLAGS_EXTRA += -Wno-unused-function | |
| # LVGL SRC | ||
| SRC_USERMOD_LIB_C += $(shell find $(LVGL_DIR)/src -type f -name "*.c") | ||
|
|
||
| # Binding-friendly wrappers for driver functions gen_mpy can't bind as-is | ||
| SRC_USERMOD_LIB_C += $(LVGL_BINDING_DIR)/gen/lv_mpy_drivers.c | ||
|
|
||
| # LVGL GENERIC DRIVER | ||
| SRC_USERMOD_LIB_C += $(shell find $(LVGL_GENERIC_DRV_DIR) -type f -name "*.c") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| import os | ||
| import lvgl as lv | ||
|
|
||
| MODE = "interactive" | ||
| POINTER = "sim" | ||
| # "sdl" (default) or "wayland" -- set via env var so CI can exercise the | ||
| # same tests against both desktop drivers without duplicating test files. | ||
| DRIVER = os.getenv("LV_MP_TEST_DRIVER", "sdl") | ||
| WIDTH = 240 | ||
| HEIGHT = 320 | ||
| COLOR_FORMAT = lv.COLOR_FORMAT.RGB888 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| import os | ||
| import lvgl as lv | ||
|
|
||
| MODE = "interactive" | ||
| POINTER = "interactive" | ||
| # "sdl" (default) or "wayland" -- set via env var so CI can exercise the | ||
| # same tests against both desktop drivers without duplicating test files. | ||
| DRIVER = os.getenv("LV_MP_TEST_DRIVER", "sdl") | ||
| WIDTH = 240 | ||
| HEIGHT = 320 | ||
| COLOR_FORMAT = lv.COLOR_FORMAT.RGB888 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The
sleep 1before running Wayland tests creates a race condition. On a loaded CI runner, Weston may take more than 1 second to initialize its socket, causing the test command to fail with a connection error. Replace the fixed sleep with a polling loop that waits for the socket file$XDG_RUNTIME_DIR/wayland-cito appear (with a timeout).Prompt for AI agents