diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4db4186..3d2d7a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,191 +8,212 @@ on: workflow_dispatch: jobs: - # ── macOS ─────────────────────────────────────────────────── - macos: - runs-on: macos-latest + # ── macOS arm64 + x64 (matrix) ────────────────────────────────────────────── + # ── macOS arm64 ───────────────────────────────────────────────────────────── + # We ship arm64 only on macOS. Apple Silicon shipped in 2020; users on Intel + # Macs without Rosetta 2 are vanishingly rare for a hobby project. The + # GitHub-hosted macos-13 (Intel) free runner queue is currently 1h+, which + # blocked PRs for hours; building on macos-14 (arm64) keeps CI predictable. + # If we ever need x64, build it offline from a developer machine and attach + # to the release manually, or revisit the matrix. + macos-arm64: + name: macos-arm64 + runs-on: macos-14 steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dependencies - run: brew install sdl2 sdl2_image freetype pkg-config - - name: Build - run: | - mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make -j$(sysctl -n hw.ncpu) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ - cp -r build/assets dist/M5CardputerZero-Emulator/ - cp -r build/share dist/M5CardputerZero-Emulator/ - cd dist && tar czf ../M5CardputerZero-Emulator-macOS.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-macos - path: M5CardputerZero-Emulator-macOS.tar.gz + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install dependencies + run: | + brew update >/dev/null + brew install sdl2 sdl2_image freetype pkg-config + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(sysctl -n hw.ncpu) + - name: Bundle dylibs + run: | + set -euxo pipefail + DIST=dist/M5CardputerZero-Emulator + mkdir -p "$DIST" + cp build/cardputer-emu "$DIST/" + cp -r build/apps "$DIST/" + cp -r build/assets "$DIST/" + cp -r build/share "$DIST/" + # Copy non-system dylib deps next to the binary and rewrite rpaths. + if ! command -v dylibbundler >/dev/null 2>&1; then + brew install dylibbundler + fi + dylibbundler -od -b \ + -x "$DIST/cardputer-emu" \ + -x "$DIST/apps/libAPPLaunch.dylib" \ + -x "$DIST/apps/libUserDemo.dylib" \ + -d "$DIST/libs" -p "@executable_path/libs/" || \ + echo "[warn] dylibbundler returned non-zero (continuing — deps may resolve already)" + - name: Package + run: | + cd dist && tar czf ../M5CardputerZero-Emulator-macOS-arm64.tar.gz M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-macos-arm64 + path: M5CardputerZero-Emulator-macOS-arm64.tar.gz - # ── Windows (MinGW-w64) ───────────────────────────────────── + # ── Windows x64 (MinGW-w64) ──────────────────────────────────────────────── windows: + name: windows-x64 runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - install: >- - mingw-w64-x86_64-gcc - mingw-w64-x86_64-cmake - mingw-w64-x86_64-SDL2 - mingw-w64-x86_64-SDL2_image - mingw-w64-x86_64-freetype - mingw-w64-x86_64-winpthreads-git - make - git - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Build - run: | - mkdir build && cd build - cmake .. -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release - make -j$(nproc) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu.exe dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ 2>/dev/null || true - cp -r build/assets dist/M5CardputerZero-Emulator/ 2>/dev/null || true - cp -r build/share dist/M5CardputerZero-Emulator/ 2>/dev/null || true - # Copy all DLL dependencies automatically - ldd build/cardputer-emu.exe | grep mingw64 | awk '{print $3}' | while read dll; do - cp "$dll" dist/M5CardputerZero-Emulator/ - done - cd dist && tar czf ../M5CardputerZero-Emulator-Windows.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-windows - path: M5CardputerZero-Emulator-Windows.tar.gz + - uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: >- + mingw-w64-x86_64-gcc + mingw-w64-x86_64-cmake + mingw-w64-x86_64-SDL2 + mingw-w64-x86_64-SDL2_image + mingw-w64-x86_64-freetype + mingw-w64-x86_64-winpthreads-git + make + git + zip + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Build + run: | + mkdir build && cd build + cmake .. -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release + make -j$(nproc) + - name: Bundle DLLs + run: | + set -euxo pipefail + DIST=dist/M5CardputerZero-Emulator + mkdir -p "$DIST" + cp build/cardputer-emu.exe "$DIST/" + cp -r build/apps "$DIST/" 2>/dev/null || true + cp -r build/assets "$DIST/" 2>/dev/null || true + cp -r build/share "$DIST/" 2>/dev/null || true + # Walk the dependency tree and copy every non-system DLL next to the exe. + ldd build/cardputer-emu.exe | awk '/mingw64/ {print $3}' | while read -r dll; do + cp "$dll" "$DIST/" + done + # Second pass for transitive deps of the copied DLLs. + for dll in "$DIST"/*.dll; do + ldd "$dll" 2>/dev/null | awk '/mingw64/ {print $3}' | while read -r d; do + [ -f "$DIST/$(basename "$d")" ] || cp "$d" "$DIST/" + done + done + - name: Package + run: | + cd dist && zip -rq ../M5CardputerZero-Emulator-Windows-x64.zip M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-windows-x64 + path: M5CardputerZero-Emulator-Windows-x64.zip - # ── Linux ─────────────────────────────────────────────────── + # ── Linux x64 ─────────────────────────────────────────────────────────────── linux: - runs-on: ubuntu-latest + name: linux-x64 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y libsdl2-dev libsdl2-image-dev libfreetype-dev libinput-dev libxkbcommon-dev pkg-config - - name: Build - run: | - mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make -j$(nproc) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ - cp -r build/assets dist/M5CardputerZero-Emulator/ - cp -r build/share dist/M5CardputerZero-Emulator/ - cd dist && tar czf ../M5CardputerZero-Emulator-Linux.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-linux - path: M5CardputerZero-Emulator-Linux.tar.gz + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsdl2-dev libsdl2-image-dev libfreetype-dev \ + libasound2-dev libinput-dev libxkbcommon-dev pkg-config + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(nproc) + - name: Package + run: | + mkdir -p dist/M5CardputerZero-Emulator + cp build/cardputer-emu dist/M5CardputerZero-Emulator/ + cp -r build/apps dist/M5CardputerZero-Emulator/ + cp -r build/assets dist/M5CardputerZero-Emulator/ + cp -r build/share dist/M5CardputerZero-Emulator/ + cd dist && tar czf ../M5CardputerZero-Emulator-Linux-x64.tar.gz M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-linux-x64 + path: M5CardputerZero-Emulator-Linux-x64.tar.gz - # ── Web (Emscripten/WASM) ─────────────────────────────────── + # ── Web (Emscripten/WASM) ─────────────────────────────────────────────────── web: - runs-on: ubuntu-latest + name: web-wasm + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Setup Emscripten - uses: mymindstorm/setup-emsdk@v14 - with: - version: 3.1.73 - - name: Build - run: | - mkdir build-web && cd build-web - emcmake cmake .. -DCMAKE_BUILD_TYPE=Release - emmake make -j$(nproc) - - name: Package - run: | - # Standalone emulator - mkdir -p dist-web - cp build-web/index.html dist-web/ - cp build-web/index.js dist-web/ - cp build-web/index.wasm dist-web/ - cp build-web/index.data dist-web/ - cd dist-web && zip -r ../M5CardputerZero-Emulator-Web.zip . - cd .. - # Playground (embeds emulator in iframe) - mkdir -p dist-playground/emulator - cp web-playground/index.html dist-playground/ - cp build-web/index.html dist-playground/emulator/ - cp build-web/index.js dist-playground/emulator/ - cp build-web/index.wasm dist-playground/emulator/ - cp build-web/index.data dist-playground/emulator/ - cd dist-playground && zip -r ../M5CardputerZero-Playground.zip . - - uses: actions/upload-artifact@v4 - with: - name: emulator-web - path: M5CardputerZero-Emulator-Web.zip - - uses: actions/upload-artifact@v4 - with: - name: playground - path: M5CardputerZero-Playground.zip + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Setup Emscripten + uses: mymindstorm/setup-emsdk@v14 + with: + version: 3.1.73 + - name: Build + run: | + mkdir build-web && cd build-web + emcmake cmake .. -DCMAKE_BUILD_TYPE=Release + emmake make -j$(nproc) + - name: Package + run: | + mkdir -p dist-web + cp build-web/index.html build-web/index.js build-web/index.wasm build-web/index.data dist-web/ + (cd dist-web && zip -rq ../M5CardputerZero-Emulator-Web.zip .) + mkdir -p dist-playground/emulator + cp web-playground/index.html dist-playground/ + cp build-web/index.html build-web/index.js build-web/index.wasm build-web/index.data dist-playground/emulator/ + (cd dist-playground && zip -rq ../M5CardputerZero-Playground.zip .) + - uses: actions/upload-artifact@v4 + with: + name: emulator-web + path: M5CardputerZero-Emulator-Web.zip + - uses: actions/upload-artifact@v4 + with: + name: playground + path: M5CardputerZero-Playground.zip - # ── Pre-release (runs after all 4 builds succeed) ─────────── + # ── Pre-release (after every native + web build is green) ─────────────────── release: - needs: [macos, windows, linux, web] - runs-on: ubuntu-latest + needs: [macos-arm64, windows, linux, web] + runs-on: ubuntu-22.04 if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Create pre-release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euxo pipefail + TAG="dev-$(date -u +%Y%m%d-%H%M%S)-${GITHUB_SHA:0:7}" + echo "Tag: $TAG" + gh release create "$TAG" \ + --title "Dev Build $(date -u +%Y-%m-%d) (${GITHUB_SHA:0:7})" \ + --notes "Automated pre-release from commit ${GITHUB_SHA:0:7} - - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Create pre-release - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="dev-$(date -u +%Y%m%d-%H%M%S)-$(echo $GITHUB_SHA | head -c 7)" - echo "Tag: $TAG" - - # Delete previous dev release if exists - gh release delete dev-latest --yes 2>/dev/null || true - git push origin :refs/tags/dev-latest 2>/dev/null || true - - # Create new pre-release - gh release create "$TAG" \ - --title "Dev Build $(date -u +%Y-%m-%d) (${GITHUB_SHA:0:7})" \ - --notes "Automated pre-release from commit ${GITHUB_SHA:0:7} - - **Downloads:** - - macOS (arm64): \`M5CardputerZero-Emulator-macOS.tar.gz\` - - Linux (x86_64): \`M5CardputerZero-Emulator-Linux.tar.gz\` - - Windows (x64, MinGW): \`M5CardputerZero-Emulator-Windows.tar.gz\` - - Web (WASM): \`M5CardputerZero-Emulator-Web.zip\` - - - **Playground** (hosted): \`M5CardputerZero-Playground.zip\` — unzip and open index.html - - _Windows build includes UserDemo only (APPLaunch requires Unix APIs)._" \ - --prerelease \ - artifacts/emulator-macos/M5CardputerZero-Emulator-macOS.tar.gz \ - artifacts/emulator-linux/M5CardputerZero-Emulator-Linux.tar.gz \ - artifacts/emulator-windows/M5CardputerZero-Emulator-Windows.tar.gz \ - artifacts/emulator-web/M5CardputerZero-Emulator-Web.zip \ - artifacts/playground/M5CardputerZero-Playground.zip + **Downloads:** + - macOS arm64: \`M5CardputerZero-Emulator-macOS-arm64.tar.gz\` + - Linux x64: \`M5CardputerZero-Emulator-Linux-x64.tar.gz\` + - Windows x64 (MinGW): \`M5CardputerZero-Emulator-Windows-x64.zip\` + - Web (WASM): \`M5CardputerZero-Emulator-Web.zip\` + - Playground (hosted): \`M5CardputerZero-Playground.zip\`" \ + --prerelease \ + artifacts/emulator-macos-arm64/M5CardputerZero-Emulator-macOS-arm64.tar.gz \ + artifacts/emulator-linux-x64/M5CardputerZero-Emulator-Linux-x64.tar.gz \ + artifacts/emulator-windows-x64/M5CardputerZero-Emulator-Windows-x64.zip \ + artifacts/emulator-web/M5CardputerZero-Emulator-Web.zip \ + artifacts/playground/M5CardputerZero-Playground.zip diff --git a/.gitignore b/.gitignore index 274bcd5..d4db850 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 441870e..d003759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -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 @@ -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) @@ -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) @@ -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 @@ -310,7 +311,7 @@ add_custom_command(TARGET cardputer-emu POST_BUILD $/apps/ COMMAND ${CMAKE_COMMAND} -E make_directory $/share/images COMMAND ${CMAKE_COMMAND} -E copy_directory - ${APPLAUNCH}/ui/images + ${APPLAUNCH_IMAGES_DIR} $/share/images COMMAND ${CMAKE_COMMAND} -E make_directory $/assets COMMAND ${CMAKE_COMMAND} -E copy @@ -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 @@ -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 @@ -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 ) diff --git a/cmake/launcher_sources.cmake b/cmake/launcher_sources.cmake new file mode 100644 index 0000000..a3e0bef --- /dev/null +++ b/cmake/launcher_sources.cmake @@ -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() diff --git a/cmake/patches/apply_vendor_workarounds.cmake b/cmake/patches/apply_vendor_workarounds.cmake new file mode 100644 index 0000000..7cf234c --- /dev/null +++ b/cmake/patches/apply_vendor_workarounds.cmake @@ -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() diff --git a/cmake/third_party.cmake b/cmake/third_party.cmake new file mode 100644 index 0000000..30aa6d4 --- /dev/null +++ b/cmake/third_party.cmake @@ -0,0 +1,82 @@ +# ───────────────────────────────────────────────────────────────────────────── +# third_party.cmake +# ───────────────────────────────────────────────────────────────────────────── +# Owns every third-party / compat dependency APPLaunch needs but the emulator's +# own source tree doesn't ship with directly. Centralized so that adding a new +# Launcher dependency = one entry here, not three target_include_directories +# calls scattered across CMakeLists.txt. +# +# Provides three INTERFACE libraries: +# emu::compat_stubs — thpool stub, sys/wait.h pass-through, nlohmann shim +# emu::miniaudio — miniaudio.h include + per-platform audio framework links +# emu::launcher_apply — convenience: apply both, plus emu_compat.h force-include +# +# Inputs expected: +# APPLAUNCH_THIRDPARTY_INCLUDES (from launcher_sources.cmake) +# ───────────────────────────────────────────────────────────────────────────── + +# ── compat stubs (thpool, sys/wait, nlohmann shim) ─────────────────────────── +add_library(emu_compat_stubs INTERFACE) +add_library(emu::compat_stubs ALIAS emu_compat_stubs) + +# IMPORTANT: src/compat houses Windows-only overrides for system headers +# (sys/wait.h, sys/queue.h). Adding it to the include path on macOS/Linux +# would shadow the real system headers and break compilation. Gate it. +if(WIN32) + target_include_directories(emu_compat_stubs INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/src/compat + ) +endif() + +# thpool stub from the launcher submodule's emu sub-project — header-only, +# safe to expose on every platform (declares a no-op threadpool). +set(_launcher_emu_compat_stubs + ${VENDOR_DIR}/projects/CardputerZero-Emulator/src/compat_stubs) +if(EXISTS "${_launcher_emu_compat_stubs}/thpool.h") + target_include_directories(emu_compat_stubs INTERFACE + ${_launcher_emu_compat_stubs} + ) +endif() + +# nlohmann (header-only, vendored by user under vendor/nlohmann/json.hpp if needed) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor/nlohmann/json.hpp) + target_include_directories(emu_compat_stubs INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/vendor + ) +endif() + +# ── miniaudio (header-only; .c TU does #define MINIAUDIO_IMPLEMENTATION) ───── +add_library(emu_miniaudio INTERFACE) +add_library(emu::miniaudio ALIAS emu_miniaudio) + +target_include_directories(emu_miniaudio INTERFACE + ${APPLAUNCH_THIRDPARTY_INCLUDES} +) + +# Platform audio backends — required because miniaudio's IMPLEMENTATION TU +# pulls in OS audio APIs. +if(APPLE) + target_link_libraries(emu_miniaudio INTERFACE + "-framework CoreAudio" + "-framework AudioToolbox" + "-framework AudioUnit" + "-framework CoreFoundation" + ) +elseif(UNIX AND NOT EMSCRIPTEN) + # Linux: ALSA (default), pulse optional. miniaudio compiles whatever is + # available; ALSA dev headers must be installed. + target_link_libraries(emu_miniaudio INTERFACE asound dl pthread m) +elseif(EMSCRIPTEN) + # Web: miniaudio supports Web Audio. No extra link needed beyond -pthread + # which is set globally. +endif() +# Windows (MinGW/MSVC): miniaudio uses WASAPI/DirectSound, no -l needed. + +# ── one-shot bundle for APPLaunch-style targets ────────────────────────────── +# Use this on any target that compiles ui_events.c + miniaudio + thpool. +add_library(emu_launcher_apply INTERFACE) +add_library(emu::launcher_apply ALIAS emu_launcher_apply) +target_link_libraries(emu_launcher_apply INTERFACE + emu::compat_stubs + emu::miniaudio +) diff --git a/src/emu_launcher_stubs.c b/src/emu_launcher_stubs.c new file mode 100644 index 0000000..ccbc7bf --- /dev/null +++ b/src/emu_launcher_stubs.c @@ -0,0 +1,30 @@ +/* + * emu_launcher_stubs.c — definitions of symbols that Launcher's main.cpp + * provides on the device but the emulator can't pull in (the emulator runs + * its own src/main.cpp, not Launcher's). + * + * Only linked into statically-built emulator targets (Windows, Web). The + * Unix shared-library path resolves these at dlopen time inside libAPPLaunch + * via dynamic_lookup / --unresolved-symbols=ignore-all, so it doesn't need + * this TU. + * + * If Launcher introduces new globals normally defined by its main.cpp, add + * them here. + */ + +#include + +/* Custom LVGL event codes registered at runtime by Launcher's main.cpp. + * The headers declare them as `extern volatile uint32_t`, so the loader + * just needs zero-initialized storage. They're populated via + * lv_event_register_id() before APPLaunch fires its first battery/wifi + * event — code paths the emulator stub'd HAL never actually drives. */ +volatile uint32_t LV_EVENT_BATTERY = 0; +volatile uint32_t LV_EVENT_WIFI_INFO = 0; + +/* Launcher uses a global thread pool for off-loading startup work. With our + * thpool stub (cmake/third_party.cmake → emu::compat_stubs), thpool_init() + * returns NULL and thpool_add_work() runs the callback synchronously, so a + * plain NULL is the correct emulator value. */ +typedef void *threadpool; +threadpool g_launch_thread_pool = (threadpool)0; diff --git a/vendor/M5CardputerZero-Launcher b/vendor/M5CardputerZero-Launcher index cff863b..92d4434 160000 --- a/vendor/M5CardputerZero-Launcher +++ b/vendor/M5CardputerZero-Launcher @@ -1 +1 @@ -Subproject commit cff863b29472c45306f67b9ad550c4add59accfb +Subproject commit 92d4434ef110f159b6f27769dea5b7f26ca292f1