Files
LiHaohuaandClaude Opus 4.7 f12cf51e16 SDK: sync lv_conf demo flags, expose demos/ include, add NO_KBD_STUBS opt-out
sdk/include/lv_conf.h mirrors the emulator's (music + keypad_encoder
demos enabled) so apps using the SDK see the same LVGL feature set
the host provides.

sdk/cmake/CZApp.cmake gains:
- demos/ header search path so apps can #include
  "widgets/lv_demo_widgets.h" etc.
- NO_KBD_STUBS option for apps that want to resolve the host's real
  lv_sdl_keyboard_create at dlopen time (instead of the weak inert
  stub). Default weak stub stays for the 90% of apps that don't care
  about keyboard input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:30:34 +08:00

135 lines
5.6 KiB
CMake

# CZApp.cmake — build a CardputerZero LVGL app as a shared library.
#
# Usage:
# list(APPEND CMAKE_MODULE_PATH "${path_to_sdk}/cmake")
# include(CZApp)
# cz_add_lvgl_app(my_app SOURCES src/foo.c src/bar.c)
#
# The resulting target:
# * is a SHARED library named "my_app" (→ libmy_app.dylib / .so / my_app.dll)
# * can reference any LVGL API — LVGL symbols are resolved at dlopen time
# against the host's (emulator or APPLaunch) LVGL instance
# * exports exactly the symbols declared in <cz_app.h>
#
# Required variables (auto-discovered from this file's location):
# CZ_SDK_DIR — root of the sdk/ tree (include/ + cmake/ below it)
# CZ_EMULATOR_DIR — the emulator checkout that supplies lvgl.h. Defaults to
# ${CZ_SDK_DIR}/../emulator (AppBuilder layout).
#
# See docs/DESKTOP_DEV.md for the rules an app must follow.
if(NOT DEFINED CZ_SDK_DIR)
get_filename_component(CZ_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
endif()
if(NOT DEFINED CZ_EMULATOR_DIR)
get_filename_component(CZ_EMULATOR_DIR "${CZ_SDK_DIR}/../emulator" ABSOLUTE)
endif()
set(CZ_LVGL_DIR "${CZ_EMULATOR_DIR}/lib/lvgl")
if(NOT EXISTS "${CZ_LVGL_DIR}/lvgl.h")
message(FATAL_ERROR
"CZApp: LVGL headers not found at ${CZ_LVGL_DIR}/lvgl.h. "
"Did you run `git submodule update --init --recursive` in the "
"CardputerZero-AppBuilder checkout?")
endif()
function(cz_add_lvgl_app TARGET)
# Options:
# NO_KBD_STUBS — skip the default weak lv_sdl_keyboard_create/handler
# shim. Apps that want real keyboard input (using the
# emulator's real SDL keyboard driver) should set this so
# that the symbols stay unresolved in the .dylib/.so and
# get bound to the host at dlopen time.
cmake_parse_arguments(CZA "NO_KBD_STUBS" "" "SOURCES" ${ARGN})
if(NOT CZA_SOURCES)
message(FATAL_ERROR "cz_add_lvgl_app(${TARGET}): no SOURCES given")
endif()
add_library(${TARGET} SHARED ${CZA_SOURCES})
set_target_properties(${TARGET} PROPERTIES
C_STANDARD 11
CXX_STANDARD 17
PREFIX "lib" # Windows: libfoo.dll for parity with Unix
POSITION_INDEPENDENT_CODE ON
)
target_include_directories(${TARGET} PRIVATE
"${CZ_SDK_DIR}/include" # cz_app.h + lv_conf.h
"${CZ_LVGL_DIR}" # lvgl.h
"${CZ_LVGL_DIR}/demos" # demos/widgets/lv_demo_widgets.h etc.
)
# Pinned LVGL config — must byte-match the host's.
target_compile_definitions(${TARGET} PRIVATE LV_CONF_INCLUDE_SIMPLE=1)
# Link-time behaviour: LVGL symbols stay unresolved in the app library; the
# host resolves them at dlopen/LoadLibrary time.
if(APPLE)
target_link_options(${TARGET} PRIVATE -undefined dynamic_lookup)
elseif(WIN32)
# Windows needs an import library for LVGL; see docs/DESKTOP_DEV.md §4.
# Until that lands, flag builds so they fail loud instead of producing
# a silently broken .dll.
message(WARNING
"cz_add_lvgl_app: Windows import-lib support is not implemented "
"yet (see DESKTOP_DEV.md §4). Build will likely fail at link.")
else()
target_link_options(${TARGET}
PRIVATE "LINKER:--unresolved-symbols=ignore-all")
endif()
# Compatibility shim: the current emulator dlsym's "ui_init" and calls it.
# Until the emulator switches to app_main/app_event (separate task), emit
# a one-line ui_init thunk that forwards to app_main(lv_screen_active()).
set(_thunk "${CMAKE_CURRENT_BINARY_DIR}/cz_${TARGET}_ui_init_thunk.c")
file(WRITE "${_thunk}"
"/* Auto-generated by CZApp.cmake — do not edit. */\n"
"#include <cz_app.h>\n"
"void ui_init(void) { app_main(lv_screen_active()); }\n"
)
target_sources(${TARGET} PRIVATE "${_thunk}")
# Also emit a default weak app_event so apps that only care about UI can
# skip providing one. Strong definitions in the app override this.
set(_weak "${CMAKE_CURRENT_BINARY_DIR}/cz_${TARGET}_app_event_default.c")
file(WRITE "${_weak}"
"/* Auto-generated by CZApp.cmake — default no-op app_event. */\n"
"#include <cz_app.h>\n"
"__attribute__((weak)) void app_event(int type, void *data) { (void)type; (void)data; }\n"
)
target_sources(${TARGET} PRIVATE "${_weak}")
if(CZA_NO_KBD_STUBS)
return()
endif()
# Default weak keyboard stubs. The emulator always dlsyms
# lv_sdl_keyboard_create / lv_sdl_keyboard_handler; when the app provides
# neither, the emulator falls back to a bare keypad indev with no read_cb
# and LVGL floods the log with "indev_read_cb is not registered". These
# stubs give the emulator a real (but inert) indev so the log stays clean.
# Apps that want real keyboard input pass NO_KBD_STUBS and let the
# emulator's real lv_sdl_keyboard_* resolve via dlopen.
set(_kbd "${CMAKE_CURRENT_BINARY_DIR}/cz_${TARGET}_kbd_default.c")
file(WRITE "${_kbd}"
"/* Auto-generated by CZApp.cmake — default no-op SDL keyboard stubs. */\n"
"#include <cz_app.h>\n"
"static void _cz_default_kbd_read(lv_indev_t *indev, lv_indev_data_t *data) {\n"
" (void)indev;\n"
" data->state = LV_INDEV_STATE_RELEASED;\n"
" data->key = 0;\n"
"}\n"
"__attribute__((weak)) lv_indev_t *lv_sdl_keyboard_create(void) {\n"
" lv_indev_t *kb = lv_indev_create();\n"
" lv_indev_set_type(kb, LV_INDEV_TYPE_KEYPAD);\n"
" lv_indev_set_read_cb(kb, _cz_default_kbd_read);\n"
" return kb;\n"
"}\n"
"__attribute__((weak)) void lv_sdl_keyboard_handler(void *ev) { (void)ev; }\n"
)
target_sources(${TARGET} PRIVATE "${_kbd}")
endfunction()