Skip to content

Desktop dev loop: czdev CLI + emulator + SDK + NC2000/APPLaunch/LVGL demos#1

Merged
eggfly merged 15 commits intomainfrom
ci/desktop-dev
May 6, 2026
Merged

Desktop dev loop: czdev CLI + emulator + SDK + NC2000/APPLaunch/LVGL demos#1
eggfly merged 15 commits intomainfrom
ci/desktop-dev

Conversation

@eggfly
Copy link
Copy Markdown
Collaborator

@eggfly eggfly commented May 6, 2026

Lays down a working desktop development environment for CardputerZero LVGL apps on macOS and Linux — clone, run one command, see a 320×170 LCD skin with your app loaded. Same source tree builds into an aarch64 .deb via the existing CI. Windows is pre-wired with a known-issue plan (T08 in docs/DESKTOP_DEV.md §4).

What you can run today

git checkout ci/desktop-dev
git submodule update --init --recursive
cargo run -p czdev --release -- doctor
cargo run -p czdev --release -- run examples/hello_cz       # minimal cz_app.h
cargo run -p czdev --release -- run examples/lvgl_widgets   # lv_demo_widgets
cargo run -p czdev --release -- run examples/lvgl_music     # lv_demo_music
cargo run -p czdev --release -- run examples/key_echo       # lv_demo_keypad_encoder
cargo run -p czdev --release -- run apps/applaunch          # real APPLaunch (from UserDemo)
cargo run -p czdev --release -- run apps/nc2000             # Wenquxing NC2000 emulator

All verified running locally (macOS/arm64). CI run: https://github.com/m5stack/CardputerZero-AppBuilder/actions/runs/25414878081 (mac + Linux smoke green).

Architecture

Piece What it is
emulator/ (submodule) SDL2 + LVGL 9.5 host, force-loads lvgl + lvgl_demos so dlopen'd apps can use built-in widgets/music/keypad demos as host symbols
sdk/include/cz_app.h Public C ABI: app_main(lv_obj_t *parent) + app_event(int, void*) + CZ_EV_* enum — same contract on desktop and on the device
sdk/include/lv_conf.h Pinned LVGL v9.5 config, byte-matches the emulator's
sdk/cmake/CZApp.cmake cz_add_lvgl_app(name SOURCES ...) one-liner — handles per-OS link flags, generates ui_init thunk, default weak keyboard stubs with a real indev read_cb
crates/czdev/ Rust CLI in a Cargo workspace alongside src-tauri/doctor / list / build / run / watch / deploy
apps/applaunch/ Zero edits to UserDemo — reuses the libAPPLaunch.dylib the emulator's own CMake builds; new runtime: "prebuilt-emulator-app"
apps/nc2000/ Submodule to CardputerZero-NC2000:ci-cd which adds port_lvgl/ alongside existing port_fb/ — produces libnc2000.dylib implementing the cz_app.h ABI, NC2000_ROM_DIR auto-set via manifest env

Manifest schema (app-builder.json)

Backward-compatible extension: existing packaging fields (package_name, version, bin_name, …) keep working. New optional fields for desktop dev:

  • runtime: "lvgl-dlopen" (default), "legacy-deb-only", "prebuilt-emulator-app"
  • entry / event_entry: C symbol names (default app_main / app_event)
  • lvgl_version: host must match on major.minor (currently "9.5")
  • cmake_args: extra -DX=Y args with ${CZ_SDK_DIR} / ${CZ_LVGL_DIR} substitution
  • cmake_target: override the CMake target name
  • env: runtime env vars with ${APP_DIR} substitution
  • caps: declared capabilities (keyboard, audio, network, …) — metadata now, wiring later

Full schema: docs/APP_BUILDER_JSON.md.

What's explicitly out of scope this round

Listed in docs/DESKTOP_DEV.md §5:

  • Python / Qt / Tkinter / raw-framebuffer examples (stays on CardputerZero-Examples/scripts/dev-on-mac/ Docker path)
  • Subprocess + SHM runtime channel (dlopen covers 100% of LVGL apps)
  • Hot dlclose / live reload (czdev watch restarts the emulator instead)
  • Full Windows support: LVGL DLL rework is planned as T08 with an 8-issue table in docs/DESKTOP_DEV.md §4. The CI has a windows-todo placeholder job that surfaces this rather than failing silently.

CI

.github/workflows/desktop-smoke.yml — mac + Linux matrix, builds the emulator + hello_cz via czdev, headless-launches with SDL_VIDEODRIVER=dummy and asserts the process survives 5 s.

Submodule notes

  • emulator/ pinned to eggfly/M5CardputerZero-Emulator:ci-cd (repo unarchived for this PR; two-commit diff: force-load lvgl_demos + enable music/keypad demo flags).
  • apps/nc2000/ tracks eggfly/CardputerZero-NC2000:ci-cd (adds port_lvgl/ and its manifest; leaves port_fb/ untouched).
  • M5CardputerZero-UserDemo is not modified — APPLaunch is consumed via the emulator submodule's existing vendor path.

🤖 Generated with Claude Code

eggfly and others added 15 commits April 29, 2026 12:00
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update product name, identifier, and window title
- Add build-tauri.yml: builds on Linux/macOS/Windows, creates GitHub release on tags
- Update README, docs, package.json, index.html

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tauri-apps/tauri-action expects `npm run tauri build` to work.
r.code is number | null, use nullish coalescing to default to 1.
Pins eggfly/M5CardputerZero-Emulator@318527b. Provides the SDL2+LVGL 9.5
desktop emulator and the lvgl/UserDemo nested submodules needed by the
czdev desktop dev loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DESKTOP_DEV.md freezes the app ABI (app_main + app_event + CZ_EV_*), the
LVGL-shared-via-dlopen mechanism, and the 8-issue Windows plan so future
work can't silently drift.

APP_BUILDER_JSON.md documents the backward-compatible schema extension
(runtime / entry / lvgl_version / caps / assets). QUICKSTART.md is the
3-minute onboarding for mac/Linux.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sdk/include/cz_app.h is the public ABI apps link against: two extern "C"
entry points (app_main, app_event) and the CZ_EV_* event enum.

sdk/cmake/CZApp.cmake gives app authors a one-liner
(cz_add_lvgl_app(target SOURCES ...)) that sets the right per-OS link
flags (-undefined dynamic_lookup on mac, --unresolved-symbols=ignore-all
on Linux, loud warning on Windows until T08 lands) and emits a ui_init
thunk so the current emulator keeps working while apps migrate to the
new ABI.

discover_projects.py now surfaces the new schema fields with safe
defaults so existing app-builder.json files still work unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turns the repo into a Cargo workspace with src-tauri and crates/czdev
as members. czdev is the desktop dev loop: `czdev doctor` checks SDL2
and toolchain deps with per-OS install hints; `czdev build` configures
and builds an app's shared library into .czdev/build/; `czdev run`
first-time-builds the emulator submodule, stages the .dylib/.so into
emulator/build/apps/, and launches it; `czdev watch` polls source
mtimes (no notify crate) and rebuilds+relaunches on change; `czdev
deploy` is an scp+dpkg wrapper for pre-built .debs.

.gitignore picks up target/ and per-app .czdev/ caches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three files (src/hello_cz.c + CMakeLists.txt + app-builder.json)
exercise the full SDK: implement app_main/app_event, include
sdk/cmake/CZApp.cmake, and declare runtime=lvgl-dlopen. Serves as
the canonical template for new apps and the fixture for CI smoke.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matrix job installs SDL2/freetype/cmake, runs czdev doctor, builds
hello_cz, then headless-launches the emulator with SDL_VIDEODRIVER=dummy
and asserts it survives for 5s. Windows has a separate informational
job that surfaces the pending LVGL DLL rework (T08 in DESKTOP_DEV.md §4)
rather than failing silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The emulator always dlsyms lv_sdl_keyboard_create / lv_sdl_keyboard_handler
and, when the app provides neither, falls back to a bare keypad indev with
no read_cb. LVGL then spams "indev_read_cb is not registered" every tick.

CZApp.cmake now emits weak defaults that install a proper no-op read_cb,
matching LVGL's signature (returns lv_indev_t*). Apps that want real
keyboard input override these with strong definitions at link time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d apps/nc2000

emulator submodule bumped to ci-cd branch head which enables LVGL's
built-in demos (widgets, music, keypad_encoder) as host-resolved
symbols. Apps can now declare `extern void lv_demo_widgets(void);`
and call it from app_main without vendoring demo sources.

apps/nc2000 is a new submodule pointing at CardputerZero-NC2000's
ci-cd branch, which adds the port_lvgl/ frontend producing
libnc2000.dylib/.so via cz_app.h. port_fb on the device is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 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>
…r-app runtime

Three small features so more app types can plug into the desktop loop
without touching emulator or UserDemo sources:

- Manifest gains cmake_args (extra -DX=Y) and cmake_target fields.
  NC2000 uses both: passes ENABLE_PORT_LVGL=ON, points the SDK/LVGL
  paths via ${CZ_SDK_DIR}/${CZ_LVGL_DIR} placeholders, and builds the
  nc2000_lvgl target instead of the default bin_name.
- Manifest gains `env` with ${APP_DIR} substitution so apps can
  declare runtime env vars (e.g. NC2000_ROM_DIR pointing at the
  checked-out ROMs directory).
- New `runtime: "prebuilt-emulator-app"` for apps whose library is
  built by the emulator's own CMake (e.g. APPLaunch via the UserDemo
  submodule). czdev skips cmake for these and uses the pre-staged
  file directly. Also fixed a same-path copy truncating the prebuilt
  dylib to 0 bytes on macOS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- apps/applaunch: prebuilt-emulator-app manifest that reuses the
  libAPPLaunch.dylib the emulator's own CMake produces from the
  UserDemo submodule. Zero edits to UserDemo.
- examples/lvgl_widgets: 3-line lv_demo_widgets() call, proving the
  force-loaded demo symbols from the emulator are reachable.
- examples/lvgl_music: lv_demo_music() — LVGL's built-in music UI.
- examples/key_echo: lv_demo_keypad_encoder() — keyboard-driven
  navigation demo covering the "pressing keys does something" case.

hello_cz gets a trivial text tweak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eggfly eggfly merged commit a616511 into main May 6, 2026
6 of 9 checks passed
@eggfly eggfly deleted the ci/desktop-dev branch May 6, 2026 06:34
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