Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
355 changes: 188 additions & 167 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
build/
build-*/
.DS_Store
*.o
*.a
*.dylib
build-web/

# Local-only vendored headers (legacy from previous vendor layout). Picked up
# by cmake/third_party.cmake when present but not required for the build.
/vendor/nlohmann/
75 changes: 38 additions & 37 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ set(CMAKE_C_STANDARD_REQUIRED ON)

# Paths
set(VENDOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/M5CardputerZero-Launcher)
set(APPLAUNCH ${VENDOR_DIR}/projects/APPLaunch/main)
set(USERDEMO ${VENDOR_DIR}/projects/UserDemo/main)

# Source-of-truth for which Launcher files / paths the emulator pulls in.
# Edit cmake/launcher_sources.cmake when the upstream submodule reshuffles things.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# Apply temporary patches against the vendor submodule. Each patch links to
# its tracking PR; entries are removed when upstream merges.
include(patches/apply_vendor_workarounds)
include(launcher_sources)
include(third_party)

# ---------------------------------------------------------------------------
# SDL2 + Freetype (skip for Emscripten — uses its own ports)
Expand Down Expand Up @@ -112,14 +119,9 @@ if(NOT EMSCRIPTEN)

option(EMU_SKIP_APPLAUNCH "Skip APPLaunch (Unix-only deps)" OFF)

# APPLaunch source files (used by both shared lib and Windows static link)
file(GLOB_RECURSE APPLAUNCH_UI_C ${APPLAUNCH}/ui/*.c)
file(GLOB_RECURSE APPLAUNCH_UI_CPP ${APPLAUNCH}/ui/*.cpp)
file(GLOB APPLAUNCH_KBD ${APPLAUNCH}/src/keyboard_input.c)

# HAL platform sources (SDL for emulator builds)
file(GLOB APPLAUNCH_HAL_SDL_C ${APPLAUNCH}/hal/sdl/*.c)
file(GLOB APPLAUNCH_HAL_SDL_CPP ${APPLAUNCH}/hal/sdl/*.cpp)
# APPLaunch source files come from cmake/launcher_sources.cmake (already included above):
# APPLAUNCH_UI_C / APPLAUNCH_UI_CPP / APPLAUNCH_KBD
# APPLAUNCH_HAL_SDL_C / APPLAUNCH_HAL_SDL_CPP

# ---------------------------------------------------------------------------
# APP: APPLaunch shared lib (Unix only — Windows statically links into exe)
Expand All @@ -141,11 +143,9 @@ target_include_directories(APPLaunch PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/lib
${CMAKE_CURRENT_SOURCE_DIR}/vendor
${APPLAUNCH}/ui
${APPLAUNCH}/include
${APPLAUNCH}
${APPLAUNCH}/hal
${APPLAUNCH_INCLUDES}
)
target_link_libraries(APPLaunch PRIVATE emu::launcher_apply)
target_compile_definitions(APPLaunch PRIVATE HAL_PLATFORM_SDL=1)
if(WIN32)
target_compile_options(APPLaunch PRIVATE
Expand All @@ -169,16 +169,13 @@ endif() # NOT EMU_SKIP_APPLAUNCH

# ---------------------------------------------------------------------------
# APP: UserDemo.dylib
# (USERDEMO_UI_C / USERDEMO_INCLUDES come from cmake/launcher_sources.cmake)
# ---------------------------------------------------------------------------
file(GLOB_RECURSE USERDEMO_UI_C ${USERDEMO}/ui/*.c)

add_library(UserDemo SHARED ${USERDEMO_UI_C})
target_include_directories(UserDemo PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/lib
${USERDEMO}/ui
${USERDEMO}/include
${USERDEMO}
${USERDEMO_INCLUDES}
)
if(MSVC)
target_compile_options(UserDemo PRIVATE /FI${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat.h)
Expand All @@ -200,25 +197,27 @@ endif()
# ---------------------------------------------------------------------------
if(WIN32 AND NOT EMU_SKIP_APPLAUNCH)
# Windows: statically link APPLaunch (avoids DLL duplicate LVGL globals)
add_executable(cardputer-emu src/main.cpp src/device_skin.cpp
${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD})
add_executable(cardputer-emu
src/main.cpp src/device_skin.cpp src/emu_launcher_stubs.c
${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD}
${APPLAUNCH_HAL_SDL_C} ${APPLAUNCH_HAL_SDL_CPP})
target_include_directories(cardputer-emu PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/compat # win32_stubs.h, sys/queue.h
${CMAKE_CURRENT_SOURCE_DIR}/vendor
${APPLAUNCH}/ui ${APPLAUNCH}/include ${APPLAUNCH})
${APPLAUNCH_INCLUDES})
target_link_libraries(cardputer-emu PRIVATE emu::launcher_apply)
if(WIN32)
target_compile_options(cardputer-emu PRIVATE
"SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat_win.h"
-Wno-c++11-narrowing)
endif()
target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1)
target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1 HAL_PLATFORM_SDL=1)
# MinGW: allow multiple definitions (APPLaunch overrides LVGL's SDL keyboard)
target_link_options(cardputer-emu PRIVATE -Wl,--allow-multiple-definition)
elseif(WIN32)
# Fallback: UserDemo only
add_executable(cardputer-emu src/main.cpp src/device_skin.cpp ${USERDEMO_UI_C})
target_include_directories(cardputer-emu PRIVATE
${USERDEMO}/ui ${USERDEMO}/include ${USERDEMO})
target_include_directories(cardputer-emu PRIVATE ${USERDEMO_INCLUDES})
target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1)
else()
add_executable(cardputer-emu src/main.cpp src/device_skin.cpp)
Expand Down Expand Up @@ -251,6 +250,8 @@ elseif(MSVC)
elseif(WIN32)
# MinGW: no whole-archive (app is static-linked, no DLL symbol export needed)
target_link_libraries(cardputer-emu PRIVATE lvgl lvgl_thorvg)
# WinSock for gethostname() in launcher's ui_app_hack.hpp.
target_link_libraries(cardputer-emu PRIVATE ws2_32)
else()
# Linux: --whole-archive for dlopen'd apps
target_link_libraries(cardputer-emu PRIVATE
Expand Down Expand Up @@ -310,7 +311,7 @@ add_custom_command(TARGET cardputer-emu POST_BUILD
$<TARGET_FILE_DIR:cardputer-emu>/apps/
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:cardputer-emu>/share/images
COMMAND ${CMAKE_COMMAND} -E copy_directory
${APPLAUNCH}/ui/images
${APPLAUNCH_IMAGES_DIR}
$<TARGET_FILE_DIR:cardputer-emu>/share/images
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:cardputer-emu>/assets
COMMAND ${CMAKE_COMMAND} -E copy
Expand All @@ -332,16 +333,16 @@ endif() # NOT EMSCRIPTEN — end native targets
# ---------------------------------------------------------------------------
if(EMSCRIPTEN)
# App sources statically linked (no dlopen in wasm)
file(GLOB_RECURSE APPLAUNCH_UI_C_WEB ${APPLAUNCH}/ui/*.c)
file(GLOB_RECURSE APPLAUNCH_UI_CPP_WEB ${APPLAUNCH}/ui/*.cpp)
file(GLOB APPLAUNCH_KBD_WEB ${APPLAUNCH}/src/keyboard_input.c)

# Reuse APPLAUNCH_UI_C / APPLAUNCH_UI_CPP / APPLAUNCH_KBD from launcher_sources.cmake
add_executable(cardputer-emu-web
src/main_web.cpp
src/device_skin.cpp
${APPLAUNCH_UI_C_WEB}
${APPLAUNCH_UI_CPP_WEB}
${APPLAUNCH_KBD_WEB}
src/emu_launcher_stubs.c
${APPLAUNCH_UI_C}
${APPLAUNCH_UI_CPP}
${APPLAUNCH_KBD}
${APPLAUNCH_HAL_SDL_C}
${APPLAUNCH_HAL_SDL_CPP}
)

target_include_directories(cardputer-emu-web PRIVATE
Expand All @@ -350,10 +351,10 @@ if(EMSCRIPTEN)
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/lib
${CMAKE_CURRENT_SOURCE_DIR}/vendor
${APPLAUNCH}/ui
${APPLAUNCH}/include
${APPLAUNCH}
${APPLAUNCH_INCLUDES}
)
target_link_libraries(cardputer-emu-web PRIVATE emu::launcher_apply)
target_compile_definitions(cardputer-emu-web PRIVATE HAL_PLATFORM_SDL=1)

target_compile_options(cardputer-emu-web PRIVATE
-include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat.h
Expand All @@ -373,7 +374,7 @@ if(EMSCRIPTEN)
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=67108864
"--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/assets/device_skin.png@/assets/device_skin.png"
"--preload-file=${APPLAUNCH}/ui/images@/share/images"
"--preload-file=${APPLAUNCH_IMAGES_DIR}@/share/images"
--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/web/shell.html
)

Expand Down
119 changes: 119 additions & 0 deletions cmake/launcher_sources.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# ─────────────────────────────────────────────────────────────────────────────
# launcher_sources.cmake
# ─────────────────────────────────────────────────────────────────────────────
# Single source of truth for "which Launcher files / paths the emulator pulls in".
# When the upstream M5CardputerZero-Launcher repo adds new components or moves
# files around, this file is the ONLY place that needs an update — the rest of
# CMakeLists.txt consumes the variables defined here.
#
# Inputs (must be set by caller before include()-ing this file):
# VENDOR_DIR — path to vendor/M5CardputerZero-Launcher
#
# Outputs:
# APPLAUNCH — APPLaunch project main/ dir
# USERDEMO — UserDemo project main/ dir
# APPLAUNCH_UI_C / _CPP — UI sources to compile
# APPLAUNCH_KBD — keyboard_input.c (path varies across launcher revs)
# APPLAUNCH_HAL_SDL_C / _CPP — SDL HAL sources
# USERDEMO_UI_C — UserDemo UI sources
# APPLAUNCH_INCLUDES — list of include dirs needed to compile APPLaunch
# APPLAUNCH_THIRDPARTY_INCLUDES — list of third-party include dirs (miniaudio, …)
# ─────────────────────────────────────────────────────────────────────────────

if(NOT DEFINED VENDOR_DIR)
message(FATAL_ERROR "launcher_sources.cmake: VENDOR_DIR must be set before include()")
endif()

set(APPLAUNCH ${VENDOR_DIR}/projects/APPLaunch/main)
set(USERDEMO ${VENDOR_DIR}/projects/UserDemo/main)

# ── generate page_app.h ──────────────────────────────────────────────────────
# Launcher's SConstruct runs generate_page_app_includes.py at build time to
# produce ui/components/page_app.h from the .hpp files in ui/components/page_app/.
# That file is .gitignore'd in the launcher repo (so writing it doesn't dirty
# `git status`). We replicate the generator in pure CMake here so a vanilla
# `cmake .. && make` works without invoking SCons.
set(_page_app_dir ${APPLAUNCH}/ui/components/page_app)
set(_page_app_h ${APPLAUNCH}/ui/components/page_app.h)
if(IS_DIRECTORY "${_page_app_dir}")
file(GLOB _page_app_headers
RELATIVE ${APPLAUNCH}/ui/components
CONFIGURE_DEPENDS
${_page_app_dir}/*.hpp
)
list(SORT _page_app_headers)
if(_page_app_headers)
file(WRITE "${_page_app_h}" "#pragma once\n\n")
foreach(_h ${_page_app_headers})
file(APPEND "${_page_app_h}" "#include \"${_h}\"\n")
endforeach()
else()
message(WARNING "launcher_sources.cmake: page_app/ exists but contains no .hpp files")
endif()
endif()

# ── source globs ─────────────────────────────────────────────────────────────
file(GLOB_RECURSE APPLAUNCH_UI_C ${APPLAUNCH}/ui/*.c)
file(GLOB_RECURSE APPLAUNCH_UI_CPP ${APPLAUNCH}/ui/*.cpp)

# keyboard_input.c moved between revs (src/ → hal/). Pick whichever exists.
set(APPLAUNCH_KBD "")
foreach(_kbd
${APPLAUNCH}/hal/keyboard_input.c
${APPLAUNCH}/src/keyboard_input.c)
if(EXISTS "${_kbd}")
list(APPEND APPLAUNCH_KBD "${_kbd}")
endif()
endforeach()

file(GLOB APPLAUNCH_HAL_SDL_C ${APPLAUNCH}/hal/sdl/*.c)
file(GLOB APPLAUNCH_HAL_SDL_CPP ${APPLAUNCH}/hal/sdl/*.cpp)

file(GLOB_RECURSE USERDEMO_UI_C ${USERDEMO}/ui/*.c)

# ── include dirs ─────────────────────────────────────────────────────────────
set(APPLAUNCH_INCLUDES
${APPLAUNCH}/ui
${APPLAUNCH}/include
${APPLAUNCH}
${APPLAUNCH}/hal
)

set(USERDEMO_INCLUDES
${USERDEMO}/ui
${USERDEMO}/include
${USERDEMO}
)

# ── runtime asset directories ────────────────────────────────────────────────
# Where APPLaunch expects to find PNG/font assets at runtime. Path has moved
# between launcher revs (ui/images → APPLaunch/share/images), so probe for it.
set(APPLAUNCH_IMAGES_DIR "")
foreach(_candidate
${VENDOR_DIR}/projects/APPLaunch/APPLaunch/share/images
${APPLAUNCH}/ui/images)
if(IS_DIRECTORY "${_candidate}")
set(APPLAUNCH_IMAGES_DIR "${_candidate}")
break()
endif()
endforeach()
if(NOT APPLAUNCH_IMAGES_DIR)
message(WARNING
"launcher_sources.cmake: could not locate APPLaunch images directory. "
"The emulator will run but app icons will be missing.")
endif()

# ── third-party includes shipped inside the launcher submodule ───────────────
# These are kept in a separate variable so the emulator can decide per-target
# whether to expose them.
set(APPLAUNCH_THIRDPARTY_INCLUDES "")

# Miniaudio (header-only; ui_events.c does #define MINIAUDIO_IMPLEMENTATION)
set(_miniaudio_inc ${VENDOR_DIR}/ext_components/Miniaudio/include)
if(EXISTS "${_miniaudio_inc}/miniaudio.h")
list(APPEND APPLAUNCH_THIRDPARTY_INCLUDES "${_miniaudio_inc}")
else()
message(WARNING
"launcher_sources.cmake: miniaudio.h not found at ${_miniaudio_inc}. "
"APPLaunch likely will not compile. Update the launcher submodule.")
endif()
78 changes: 78 additions & 0 deletions cmake/patches/apply_vendor_workarounds.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ─────────────────────────────────────────────────────────────────────────────
# apply_vendor_workarounds.cmake
# ─────────────────────────────────────────────────────────────────────────────
# Temporary, narrowly-scoped patches against the launcher submodule so the
# emulator can build until the upstream HAL-decouple PR lands.
#
# These patches MODIFY the submodule working tree in place (not the index).
# `git -C vendor/M5CardputerZero-Launcher status` will therefore show modified
# files — that is expected. The patches are idempotent (re-running cmake on
# an already-patched tree is a no-op) and a `git -C vendor/... checkout .`
# undoes them safely.
#
# Why working-tree edits, not an include-path overlay:
# Overlay headers are shadowed by `#include "..."` quote-form lookups that
# resolve relative to the including file's directory before consulting -I
# paths. Most launcher headers are pulled in via such relative includes, so
# an overlay can't reliably win. Modifying the vendor file in place is the
# only mechanism that reaches every consumer.
#
# Each entry below MUST link to the upstream issue/PR that will retire it.
# When the upstream PR merges and the emulator bumps the submodule SHA, the
# corresponding entry below MUST be deleted in the same commit. The function
# prints "already-fixed upstream (skip)" once that's true, which is the cue.
#
# Inputs:
# VENDOR_DIR — path to vendor/M5CardputerZero-Launcher
# ─────────────────────────────────────────────────────────────────────────────

if(NOT DEFINED VENDOR_DIR)
message(FATAL_ERROR "apply_vendor_workarounds.cmake: VENDOR_DIR must be set")
endif()

# Helper: regex-replace inside a vendor file, writing back in place. Skipped
# silently if the input regex no longer matches (post-upstream-fix safe).
function(_emu_patch_vendor_file relpath match replace why_url)
set(_path "${VENDOR_DIR}/${relpath}")
if(NOT EXISTS "${_path}")
message(STATUS "[vendor-patch] skip (file gone): ${relpath}")
return()
endif()
file(READ "${_path}" _content)
string(REGEX MATCH "${match}" _hit "${_content}")
if(NOT _hit)
# Either upstream has fixed it, or we already patched it on a prior
# cmake run — either way nothing to do.
return()
endif()
string(REGEX REPLACE "${match}" "${replace}" _patched "${_content}")
file(WRITE "${_path}" "${_patched}")
message(STATUS "[vendor-patch] applied: ${relpath} (see ${why_url})")
endfunction()

set(_any_patch_active FALSE)

# ─── No active workarounds ───────────────────────────────────────────────────
# All previous entries (#1 ui_app_setup SDL guard, #2 ui_app_launch __linux__
# guards, #3 hal_filesystem_sdl missing stdio.h) were resolved upstream by
# https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 and the
# submodule has been bumped past it.
#
# This file remains as the documented hook for the next time we need a
# narrowly-scoped patch against the launcher submodule. Each new entry MUST
# link to the upstream tracking PR that will retire it.

# ─── New entries follow the same pattern ─────────────────────────────────────
# _emu_patch_vendor_file(
# "projects/APPLaunch/main/path/to/file.cpp"
# "regex-of-broken-line"
# "replacement"
# "https://github.com/.../pull/NNN"
# )
# set(_any_patch_active TRUE)

if(_any_patch_active)
message(STATUS "[vendor-patch] one or more launcher submodule files patched in working tree.")
message(STATUS "[vendor-patch] `git -C ${VENDOR_DIR} status` will show modifications — this is expected.")
message(STATUS "[vendor-patch] Run `git -C ${VENDOR_DIR} checkout .` to revert.")
endif()
Loading
Loading