Skip to content

Feat/wayland driver binding - #413

Open
o-murphy wants to merge 2 commits into
lvgl:masterfrom
o-murphy:feat/wayland-driver-binding
Open

Feat/wayland driver binding#413
o-murphy wants to merge 2 commits into
lvgl:masterfrom
o-murphy:feat/wayland-driver-binding

Conversation

@o-murphy

@o-murphy o-murphy commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Adds LVGL's native Wayland driver as a working alternative to the SDL
driver, and fixes a regression that's been silently keeping every LVGL
desktop window driver (SDL included) out of the Python bindings entirely.

lv_sdl_window_create() can segfault deep inside the proprietary NVIDIA
driver as soon as a real window is presented, on Wayland sessions with an
NVIDIA GPU. This is #46, filed in 2019 and auto-closed stale in 2020
without a fix - I hit it firsthand and confirmed it's still present today,
5+ years later. The maintainers' own diagnosis at the time: this binding's
SDL driver deliberately runs SDL on MicroPython's own thread (for LVGL
binding thread-safety), unlike the standalone lv_drivers SDL simulator,
which refreshes on its own dedicated thread and doesn't hit the bug.
LVGL's native Wayland driver (lvgl/src/drivers/wayland) sidesteps the
whole crash class: it presents via wl_shm and never creates a GL/EGL
context at all. It's already upstream in LVGL, just never wired into this
binding's build or exposed to Python - both are fixed here.

While wiring Wayland up, I found gen_mpy.py hasn't been able to see any
desktop window driver - SDL included - since 3f32386 (2026-05-02) switched
its input from lvgl.h (which pulls in src/drivers/lv_drivers.h) to
lvgl_private.h (which only pulls in _private.h counterparts for the
libinput/evdev drivers). lv.sdl_window_create etc. have simply not
existed as Python attributes since then, regardless of LV_USE_SDL - this
also silently broke this repo's own tests/testdisplay.py's interactive
mode. Fixed by gen/lv_mpy_drivers.h, which gen_mpy now parses instead: it
pulls in lvgl_private.h and src/drivers/lv_drivers.h, so any driver
enabled in lv_conf.h gets bound the same way widgets already are.
gen/lv_mpy_drivers.c adds a binding-friendly
lv_wayland_window_create_simple() wrapper, since gen_mpy can't bind
lv_wayland_window_create()'s close-callback parameter (no
user_data-carrying struct argument for it to hook into).

micropython.mk autodetects wayland-client/wayland-cursor/xkbcommon
via pkg-config, the same pattern SDL already uses, and generates the
xdg-shell protocol glue at build time via wayland-scanner into
$(BUILD) - nothing checked into the lvgl submodule. LV_COLOR_DEPTH is
forced to 32 only when Wayland is actually compiled in, since
lv_wayland_window_create() hangs rather than failing fast on a
compositor that doesn't advertise wl_shm RGB565 support (optional
per-spec, unlike XRGB8888/ARGB8888).

This is based on #411, which this build needed to even link against a
current MicroPython. Expecting that one to land first.

Testing

Rather than duplicating tests/display/tests/indev per driver,
testdisplay.py/testrunner.py/lv_utils.py gain a DRIVER selector
("sdl"/"wayland", or the LV_MP_TEST_DRIVER env var), so the existing
suites run against either driver unchanged. Verified
tests/display/basic.py against Wayland via a headless weston
compositor - output matches the existing .exp byte for byte, no new
expected-output file needed. CI installs the Wayland build deps and
weston, and runs tests/display against Wayland in a new job step.

Doesn't attempt to fix the underlying NVIDIA/SDL bug itself (#46) - a
proprietary-driver issue outside this repo's control - or the window
decorations abort (lv_wl_window_decorations.c, unrelated, left at
upstream's own default of off).

Generative AI

I used generative AI tools when creating this PR, but a human has checked
the code and is responsible for the code and the description above.


Summary by cubic

Adds LVGL’s Wayland window driver as a safe alternative to SDL on Linux, restores desktop driver APIs to the Python bindings, and ensures compatibility with MicroPython 1.29.

  • New Features

    • Wayland driver binding: lv.wayland_window_create_simple(...), lv.wayland_pointer_create(), lv.wayland_keyboard_create(), lv.wayland_timer_handler(), lv.wayland_window_is_open().
    • Auto-enable when wayland-client, wayland-cursor, xkbcommon, wayland-scanner, and wayland-protocols are found via pkg-config; generates xdg-shell glue at build time.
    • Event loop supports a driver-specific timer via handler_cb for Wayland.
    • Tests/CI can run against wayland via LV_MP_TEST_DRIVER; CI uses headless weston.
    • Sets LV_COLOR_DEPTH=32 when Wayland is compiled to ensure a supported wl_shm format.
  • Bug Fixes

    • Restores SDL/desktop driver bindings by feeding gen_mpy gen/lv_mpy_drivers.h (includes src/drivers/lv_drivers.h) and adds a lv_wayland_window_create_simple() wrapper to avoid an unbindable close-callback.
    • Builds with MicroPython 1.29 by using mp_obj_int_to_bytes() with version guards.

Written for commit 8455c89. Summary will update on new commits.

Review in cubic

pi-anl and others added 2 commits July 20, 2026 13:14
MicroPython commit e00daa3a ("py/binary,objint: Add overflow checks and
int.to_bytes(signed=True)") renamed mp_obj_int_to_bytes_impl() to
mp_obj_int_to_bytes() with a new signature (adding is_signed and
overflow_check parameters), for the 1.29 release. The generated binding
called the old name unconditionally in mp_obj_get_ull(), so building against
1.29 or later failed to link with an undefined reference to
mp_obj_int_to_bytes_impl.

Guard the call on MICROPY_VERSION so the binding builds against both APIs.
@o-murphy

Copy link
Copy Markdown
Author

Note on the #411 dependency: it's not just a nice-to-have. master's CI is currently failing this exact way on all four ports - run 30154151496, 4 days ago, implicit declaration of function 'mp_obj_int_to_bytes'. Doesn't show red because the workflow has continue-on-error: true per job. Without #411, ports/unix doesn't build against a current MicroPython at all, so nothing in this PR could be built or tested without it.

CI here now covers both desktop drivers the same way: tests/display runs against Wayland through a headless weston compositor in a new job step, same test files SDL already used (parametrized via a DRIVER selector in testdisplay.py, not duplicated). Also caught and fixed a run-tests.py -d glob issue along the way - it was picking up display_config.py itself as a test file.

@o-murphy
o-murphy marked this pull request as ready for review July 29, 2026 13:25

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/micropython_lvgl_ci.yml">

<violation number="1" location=".github/workflows/micropython_lvgl_ci.yml:117">
P2: The `sleep 1` before 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-ci` to appear (with a timeout).</violation>
</file>

<file name="micropython.mk">

<violation number="1" location="micropython.mk:41">
P3: Incremental Wayland builds recompile generated protocol glue on every `make`, because this parse-time scanner invocation rewrites a listed C source unconditionally. Model the XML/header/source generation as Make targets with XML/scanner prerequisites so unchanged outputs are retained and compilation is ordered normally.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:195">
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.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

export XDG_RUNTIME_DIR=/tmp/xdg-runtime
weston --backend=headless --socket=wayland-ci --idle-time=0 &
WESTON_PID=$!
sleep 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The sleep 1 before 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-ci to appear (with a timeout).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/micropython_lvgl_ci.yml, line 117:

<comment>The `sleep 1` before 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-ci` to appear (with a timeout).</comment>

<file context>
@@ -102,7 +103,24 @@ jobs:
+        export XDG_RUNTIME_DIR=/tmp/xdg-runtime
+        weston --backend=headless --socket=wayland-ci --idle-time=0 &
+        WESTON_PID=$!
+        sleep 1
+        WAYLAND_DISPLAY=wayland-ci LV_MP_TEST_DRIVER=wayland \
+          MICROPY_MICROPYTHON=ports/unix/build-lvgl/micropython \
</file context>

Comment thread README.md
WIDTH = 480
HEIGHT = 320

event_loop = event_loop()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 195:

<comment>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.</comment>

<file context>
@@ -178,6 +178,57 @@ Driver code is under `/driver` directory.
+WIDTH = 480
+HEIGHT = 320
+
+event_loop = event_loop()
+disp_drv = lv.wayland_window_create_simple(WIDTH, HEIGHT, "MicroPython-LVGL")
+pointer = lv.wayland_pointer_create()
</file context>

Comment thread micropython.mk
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Incremental Wayland builds recompile generated protocol glue on every make, because this parse-time scanner invocation rewrites a listed C source unconditionally. Model the XML/header/source generation as Make targets with XML/scanner prerequisites so unchanged outputs are retained and compilation is ordered normally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At micropython.mk, line 41:

<comment>Incremental Wayland builds recompile generated protocol glue on every `make`, because this parse-time scanner invocation rewrites a listed C source unconditionally. Model the XML/header/source generation as Make targets with XML/scanner prerequisites so unchanged outputs are retained and compilation is ordered normally.</comment>

<file context>
@@ -20,6 +20,31 @@ ifeq ($(UNAME_S),Darwin)
+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)
+
+CFLAGS_USERMOD += $(WAYLAND_CFLAGS_USERMOD) -DMICROPY_WAYLAND=1 -I$(LVGL_WAYLAND_PROTOCOLS_DIR) -I$(USERMOD_DIR)/lvgl
</file context>

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.

2 participants