Skip to content
Open
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
18 changes: 16 additions & 2 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
FROM --platform=linux/x86_64 mcr.microsoft.com/devcontainers/base:ubuntu
# Pinned to 22.04 (glibc 2.35) so the Linux emulator binary built here runs on any
# reasonably current distro. An unpinned :ubuntu tag tracks the newest LTS and glibc
# keeps climbing (26.04 => 2.43), which would make the binary fail with
# "version GLIBC_x.y not found" on older hosts -- breaking the build-in-container,
# run-natively promise. Windows/macOS outputs are PE/Mach-O and don't depend on this.
FROM --platform=linux/x86_64 mcr.microsoft.com/devcontainers/base:ubuntu-22.04

# Firmware builds need cmake/build-essential/ninja + the Bootlin toolchain that
# setup.sh downloads. Everything else turns this same container into a full HDZero
# EMULATOR workbench: SDL2 to build it, ffmpeg/libav to decode a fake FPV video
# feed, and Xvfb/x11vnc/xdotool/imagemagick to run it headless, drive it, screenshot.
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get -y install --no-install-recommends cmake build-essential ninja-build libsdl2-dev
apt-get -y install --no-install-recommends \
cmake build-essential ninja-build libsdl2-dev \
wget ca-certificates bzip2 pkg-config \
libavformat-dev libavcodec-dev libavutil-dev libswscale-dev ffmpeg \
xvfb xdotool imagemagick && \
rm -rf /var/lib/apt/lists/*
65 changes: 65 additions & 0 deletions .devcontainer/Dockerfile.macos
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# syntax=docker/dockerfile:1
# macOS (arm64) cross-compile image for the HDZero SDL emulator.
#
# Containerized LLVM cross toolchain: Linux -> arm64 macOS (Mach-O). clang retargets
# via triple, ld64.lld links Mach-O — so build_xmac/HDZGOGGLE is produced entirely
# in the container, with NO compilation on the host Mac. The resulting binary RUNS
# natively on macOS (Apple Silicon).
#
# One host step is unavoidable: Apple's SDK is not redistributable, so it is staged
# from your own Xcode/CLT into MacOSX.sdk.tar (see .devcontainer/build.sh prepare-mac)
# and COPY'd in at image-build time. It is gitignored, never committed.
#
# Build (or use .devcontainer/build.sh images):
# docker build -f .devcontainer/Dockerfile.macos -t hdzero-dev-mac .devcontainer
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
clang \
lld \
llvm \
cmake \
make \
git \
ca-certificates \
curl \
xz-utils \
python3 \
&& rm -rf /var/lib/apt/lists/*

# Complete the Mach-O toolchain with LLVM's cctools-equivalents under their Apple
# names, so CMake's Darwin binutils detection (install_name_tool/otool/lipo) and
# the ld64.lld linker are all on PATH. Avoids needing osxcross's cctools port.
ENV LLVMBIN=/usr/lib/llvm-18/bin
RUN ln -sf $LLVMBIN/llvm-install-name-tool /usr/local/bin/install_name_tool \
&& ln -sf $LLVMBIN/llvm-otool /usr/local/bin/otool \
&& ln -sf $LLVMBIN/llvm-lipo /usr/local/bin/lipo \
&& ln -sf $LLVMBIN/ld64.lld /usr/local/bin/ld64.lld \
&& ln -sf /usr/bin/llvm-ar /usr/local/bin/ar \
&& ln -sf /usr/bin/llvm-ranlib /usr/local/bin/ranlib \
&& ln -sf /usr/bin/llvm-nm /usr/local/bin/nm \
&& ln -sf /usr/bin/llvm-strip /usr/local/bin/strip

# macOS SDK sysroot — staged from the host's Xcode/CLT (Apple SDK is NOT
# redistributable, so it is provided at image-build time, never committed).
COPY MacOSX.sdk.tar /tmp/MacOSX.sdk.tar
RUN mkdir -p /opt/sdk && tar xf /tmp/MacOSX.sdk.tar -C /opt/sdk && rm /tmp/MacOSX.sdk.tar
ENV MACOS_SDK=/opt/sdk/MacOSX.sdk

COPY darwin-arm64.cmake /opt/toolchain/darwin-arm64.cmake

# Cross-build SDL2 (static) for the target and install it into the image, so the
# app build can find_package(SDL2) with zero extra setup.
ARG SDL2_VERSION=2.30.10
RUN curl -fsSL https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-${SDL2_VERSION}.tar.gz -o /tmp/sdl2.tgz \
&& mkdir -p /tmp/sdl2 && tar xf /tmp/sdl2.tgz -C /tmp/sdl2 --strip-components=1 \
&& cmake -S /tmp/sdl2 -B /tmp/sdl2/build \
-DCMAKE_TOOLCHAIN_FILE=/opt/toolchain/darwin-arm64.cmake \
-DCMAKE_BUILD_TYPE=Release -DSDL_STATIC=ON -DSDL_SHARED=OFF -DSDL_TEST=OFF \
-DCMAKE_INSTALL_PREFIX=/opt/sdl2-macos \
&& cmake --build /tmp/sdl2/build -j"$(nproc)" \
&& cmake --install /tmp/sdl2/build \
&& rm -rf /tmp/sdl2 /tmp/sdl2.tgz
ENV SDL2_MACOS=/opt/sdl2-macos

WORKDIR /work
32 changes: 32 additions & 0 deletions .devcontainer/Dockerfile.windows
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# syntax=docker/dockerfile:1
# Windows (x86_64) cross-compile image for the HDZero SDL emulator.
#
# Extends the Linux dev image (hdzero-dev, from .devcontainer/Dockerfile) with the
# mingw-w64 cross toolchain + prebuilt SDL2 mingw dev libraries, so
# build_win_x64/HDZGOGGLE.exe can be produced from Linux or macOS with NO Windows
# host. The .exe then runs on Windows, including Windows-on-ARM via x64 emulation.
#
# Build order (or just use .devcontainer/build.sh images):
# docker build -f .devcontainer/Dockerfile -t hdzero-dev .devcontainer
# docker build -f .devcontainer/Dockerfile.windows -t hdzero-dev-win .devcontainer
#
# The base (hdzero-dev) is amd64 — its own Dockerfile pins FROM --platform=linux/x86_64.
# So on Apple Silicon this runs under emulation and cross-compiles the x86_64 Windows
# .exe; the "base image platform" build warning on arm64 hosts is expected and benign.
FROM hdzero-dev

# mingw-w64 cross toolchain. The package provides both win32 and -posix threading
# variants; windows-x64.cmake selects -posix so winpthread (pthread_*) is available.
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update -qq && \
apt-get install -y -qq gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 && \
rm -rf /var/lib/apt/lists/*

# Prebuilt SDL2 mingw dev tree (headers + import libs + SDL2.dll) staged where
# windows-x64.cmake looks: SDL2_DIR = /opt/sdl2-mingw/x86_64-w64-mingw32/lib/cmake/SDL2.
ARG SDL2_VERSION=2.30.9
RUN cd /tmp && \
wget -q "https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-devel-${SDL2_VERSION}-mingw.tar.gz" && \
tar xf "SDL2-devel-${SDL2_VERSION}-mingw.tar.gz" && \
mkdir -p /opt/sdl2-mingw && \
cp -r "SDL2-${SDL2_VERSION}/x86_64-w64-mingw32" /opt/sdl2-mingw/ && \
rm -rf /tmp/SDL2*
84 changes: 84 additions & 0 deletions .devcontainer/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# One reproducible entry point for every HDZero emulator build target.
# EVERY target is compiled INSIDE a devcontainer image; each RUNS natively on its OS.
#
# build.sh prepare-mac stage Apple's SDK from your Xcode into .devcontainer (one-time)
# build.sh images build all three images (hdzero-dev, hdzero-dev-win, hdzero-dev-mac)
# build.sh linux Linux emulator -> build_emu_linux/HDZGOGGLE (image: hdzero-dev)
# build.sh windows Windows emulator -> build_win_x64/HDZGOGGLE.exe (image: hdzero-dev-win, mingw)
# build.sh mac macOS emulator -> build_xmac/HDZGOGGLE (image: hdzero-dev-mac, LLVM cross)
# build.sh all images + linux + windows + mac
#
# Nothing is compiled on the host. The ONLY host-side step is prepare-mac, which
# TARs Apple's (non-redistributable) SDK out of your local Xcode so the mac image
# can COPY it in — SDK staging, not app compilation.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DC="$REPO_ROOT/.devcontainer"
DRUN() { docker run --rm -v "$REPO_ROOT:/src" -w /src "$@"; }

# Make sure the target image is present AND current before running it. This goes
# through docker's layer cache, so it's near-instant when nothing changed, but it
# rebuilds when the Dockerfile or its base image changes. That matters: a plain
# "build only if missing" check would leave a *stale* image in place after e.g. the
# glibc-floor base pin changes, silently building a binary against the wrong glibc.
# (It also avoids the confusing "pull access denied" that a bare `docker run` gives
# when the image was never built locally, since we build rather than pull.)
ensure_image() {
local img="$1" dockerfile="$2"
echo ">> ensuring image '$img' is current (${dockerfile})"
docker build -f "$DC/$dockerfile" -t "$img" "$DC"
}

prepare_mac() {
local sdk="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
[ -d "$sdk" ] || sdk="$(xcrun --show-sdk-path 2>/dev/null || true)"
[ -n "$sdk" ] && [ -d "$sdk" ] || { echo "!! no macOS SDK found — install Xcode / run 'xcode-select --install'"; exit 1; }
echo ">> staging macOS SDK: $sdk"
tar -cf "$DC/MacOSX.sdk.tar" -C "$(dirname "$sdk")" "$(basename "$sdk")"
echo ">> wrote $DC/MacOSX.sdk.tar (gitignored)"
}

images() {
docker build -f "$DC/Dockerfile" -t hdzero-dev "$DC"
docker build -f "$DC/Dockerfile.windows" -t hdzero-dev-win "$DC"
[ -f "$DC/MacOSX.sdk.tar" ] || prepare_mac
docker build -f "$DC/Dockerfile.macos" -t hdzero-dev-mac "$DC"
}

linux() {
ensure_image hdzero-dev Dockerfile
DRUN hdzero-dev bash -lc '
cmake -S . -B build_emu_linux -DEMULATOR_BUILD=ON -DHDZ_GOGGLE=ON -DCMAKE_BUILD_TYPE=Debug &&
make -C build_emu_linux -j"$(nproc)"'
}

windows() {
ensure_image hdzero-dev Dockerfile # base for the mingw image
ensure_image hdzero-dev-win Dockerfile.windows
DRUN hdzero-dev-win bash -lc '
cmake -S . -B build_win_x64 -DEMULATOR_BUILD=ON -DHDZ_GOGGLE=ON -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_TOOLCHAIN_FILE=.devcontainer/windows-x64.cmake &&
make -C build_win_x64 -j"$(nproc)"'
}

mac() {
[ -f "$DC/MacOSX.sdk.tar" ] || prepare_mac
ensure_image hdzero-dev-mac Dockerfile.macos
DRUN hdzero-dev-mac bash -lc '
cmake -S . -B build_xmac -DEMULATOR_BUILD=ON -DHDZ_GOGGLE=ON -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_TOOLCHAIN_FILE=.devcontainer/darwin-arm64.cmake &&
make -C build_xmac -j"$(nproc)"'
echo ">> built build_xmac/HDZGOGGLE (arm64 Mach-O) — run natively with: BUILD_DIR=build_xmac ./run-native.sh"
}

case "${1:-all}" in
prepare-mac) prepare_mac ;;
images) images ;;
linux) linux ;;
windows) windows ;;
mac) mac ;;
all) images; linux; windows; mac ;;
*) echo "usage: build.sh {prepare-mac|images|linux|windows|mac|all}"; exit 2 ;;
esac
52 changes: 52 additions & 0 deletions .devcontainer/darwin-arm64.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# CMake cross-compile toolchain: Linux host -> arm64 macOS (Mach-O), using LLVM.
# clang retargets via triple; ld64.lld links Mach-O; SDK provides headers/.tbd stubs.
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_PROCESSOR arm64)

# macOS SDK sysroot (extracted into the image; overridable via env)
if(DEFINED ENV{MACOS_SDK})
set(SDK "$ENV{MACOS_SDK}")
else()
set(SDK "/opt/sdk/MacOSX.sdk")
endif()

# Minimum macOS deployment target (keep conservative for wide compatibility)
set(MACOS_MIN "12.0")
set(TARGET_TRIPLE "arm64-apple-macos${MACOS_MIN}")

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_ASM_COMPILER clang)
set(CMAKE_OBJC_COMPILER clang)
set(CMAKE_OBJCXX_COMPILER clang++)
set(CMAKE_C_COMPILER_TARGET "${TARGET_TRIPLE}")
set(CMAKE_CXX_COMPILER_TARGET "${TARGET_TRIPLE}")
set(CMAKE_ASM_COMPILER_TARGET "${TARGET_TRIPLE}")
set(CMAKE_OBJC_COMPILER_TARGET "${TARGET_TRIPLE}")
set(CMAKE_OBJCXX_COMPILER_TARGET "${TARGET_TRIPLE}")

# Mach-O tooling from LLVM
set(CMAKE_AR llvm-ar)
set(CMAKE_RANLIB llvm-ranlib)
set(CMAKE_NM llvm-nm)
set(CMAKE_STRIP llvm-strip)

set(CMAKE_OSX_SYSROOT "${SDK}")
set(CMAKE_OSX_ARCHITECTURES "arm64")
set(CMAKE_OSX_DEPLOYMENT_TARGET "${MACOS_MIN}")

# Use LLVM's Mach-O linker (clang selects ld64.lld for Darwin targets with -fuse-ld=lld)
# clang derives -platform_version from the -macos<ver> triple automatically.
add_compile_options("-isysroot" "${SDK}")
add_link_options("-fuse-ld=lld" "-isysroot" "${SDK}")

set(CMAKE_FIND_ROOT_PATH "${SDK}")
# Cross-built macOS deps (e.g. SDL2) live outside the SDK sysroot; add them so
# find_package/find_library (rooted by FIND_ROOT_PATH) can locate them.
if(DEFINED ENV{SDL2_MACOS})
list(APPEND CMAKE_FIND_ROOT_PATH "$ENV{SDL2_MACOS}")
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
87 changes: 87 additions & 0 deletions .devcontainer/emu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# HDZero SDL emulator runner. Works both inside the VS Code devcontainer and under
# plain `docker run` — the repo root is derived from this script's own location, so
# it does not matter where the workspace is mounted (/workspaces/... or /src).
#
# emu.sh build configure + compile the emulator (build_emu_linux)
# emu.sh shot [name] [warmup] [keys] headless run -> screenshot emu-out/<name>.png
# emu.sh all [name] [warmup] [keys] build, then shot
#
# The container's job is BUILD + HEADLESS SCREENSHOTS (great for CI / docs / PR
# previews). Interactive use belongs to the native app: ../run-native.sh (macOS/Linux).
#
# keys: space-separated xdotool keys ("Up Up space"); prefix a token with "hold:"
# for a long press (e.g. "hold:space" = wheel long-press, which enters the FPV view).
set -euo pipefail

REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
BUILD_DIR="$REPO_ROOT/build_emu_linux"
OUT_DIR="${OUT_DIR:-$REPO_ROOT/emu-out}"
# All run-time logs go under the gitignored tmp/ tree (repo .gitignore: /tmp),
# so they never clutter the repo root or emu-out.
LOG_DIR="${LOG_DIR:-$REPO_ROOT/tmp/logs}"
DISPLAY_NUM=:99
SCREEN=1920x1080x24
mkdir -p "$OUT_DIR" "$LOG_DIR"

do_build() {
cmake -S "$REPO_ROOT" -B "$BUILD_DIR" \
-DEMULATOR_BUILD=ON -DCMAKE_BUILD_TYPE=Debug \
-DHDZ_GOGGLE=ON -DHDZ_BOXPRO=OFF -DHDZ_GOGGLE2=OFF
make -C "$BUILD_DIR" -j"$(nproc)"
}

# Stage the device runtime paths so the app finds its data where it expects on real
# hardware:
# /mnt/app -> built-in resources (OSD fonts, language) symlinked from the repo.
# Without this, load_fc_osd_font() can't find BTFL_*.bmp and the OSD
# (FC glyphs + every bmp element) renders blank.
# /mnt/extsd -> the SD card: a SAFE, gitignored copy at $HDZ_SDCARD (default
# repo/sdcard). The physical card is never exposed to the emulator.
# HDZ_MEDIA_DIR drives the playback page; HDZ_VIDEO (optional) is the fake FPV feed.
stage_runtime() {
: "${HDZ_SDCARD:=$REPO_ROOT/sdcard}"
# Point the app's configurable roots (paths_init) at the repo's built-in
# resources and the safe sandbox copy. No /mnt symlinks; the goggle build keeps
# its compiled-in /mnt/app + /mnt/extsd defaults.
export HDZ_APP_ROOT="${HDZ_APP_ROOT:-$REPO_ROOT/mkapp/app}"
export HDZ_EXTSD_ROOT="${HDZ_EXTSD_ROOT:-$HDZ_SDCARD}"
export HDZ_MEDIA_DIR="${HDZ_MEDIA_DIR:-$HDZ_SDCARD/DCIM/100HDZRO}"
export HDZ_VIDEO="${HDZ_VIDEO:-$HDZ_SDCARD/loop.mpg}"
}

do_shot() {
local name="${1:-frame}" warmup="${2:-4}" keys="${3:-}"
export DISPLAY="$DISPLAY_NUM"
stage_runtime
Xvfb "$DISPLAY_NUM" -screen 0 "$SCREEN" >"$LOG_DIR/xvfb.log" 2>&1 &
local xvfb_pid=$!
sleep 1
( cd "$BUILD_DIR" && ./HDZGOGGLE ) >"$LOG_DIR/app.log" 2>&1 &
local app_pid=$!
sleep "$warmup"
if [ -n "$keys" ]; then
for k in $keys; do
case "$k" in
hold:*) kk="${k#hold:}"; xdotool keydown "$kk"; sleep 0.8; xdotool keyup "$kk" ;;
*) xdotool key "$k" ;;
esac
sleep 0.4
done
sleep 1
fi
import -window root -display "$DISPLAY_NUM" "$OUT_DIR/${name}.png" 2>"$LOG_DIR/import.log" \
|| { xwd -root -display "$DISPLAY_NUM" | convert xwd:- "$OUT_DIR/${name}.png"; }
kill "$app_pid" 2>/dev/null || true
kill "$xvfb_pid" 2>/dev/null || true
echo "wrote $OUT_DIR/${name}.png"
echo "--- app.log (tail) ---"; tail -n 15 "$LOG_DIR/app.log" || true
}

cmd="${1:-shot}"
case "$cmd" in
build) do_build ;;
shot) shift || true; do_shot "$@" ;;
all) do_build; shift || true; do_shot "$@" ;;
*) echo "usage: emu.sh {build|shot|all} [name] [warmup] [keys]"; exit 2 ;;
esac
26 changes: 26 additions & 0 deletions .devcontainer/macos/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>HDZGOGGLE</string>
<key>CFBundleDisplayName</key>
<string>HDZero Goggle Emulator</string>
<key>CFBundleIdentifier</key>
<string>com.hdzero.goggle.emulator</string>
<key>CFBundleExecutable</key>
<string>HDZGOGGLE</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
Loading