Skip to main content

Linux and desktop

The desktop host is the analogue of a phone emulator, and the on-desktop peer of the boards: the same QuickJS bridge and C engine, with SDL2 as the backend. It is where the C side of a project can be learned before hardware arrives, and where the engine's own tests run. It builds on Linux, macOS and Windows; the folder is called linux for its origins.

Flow A: examples/linux​

The desktop "firmware" mirrors the ESP32-S3's model exactly. The executable ships no app; at start it loads a config container, app.erpkg, from a slot next to itself, the way the board loads its config partition. The same .erpkg runs here and on the device.

You need CMake 3.16+, a C compiler and SDL2 2.0.6 or newer:

sudo apt install libsdl2-dev # Debian and Ubuntu
brew install sdl2 # macOS

On Windows, install SDL2 through vcpkg and pass its toolchain file to CMake; the build copies SDL2.dll next to the executable.

Then, from the repository root:

# 1. Pack a demo into a config container
cd packages/embedded-react && npm install && npm run pack # → dist/app.erpkg
cd ../..

# 2. Build and run the host
cmake -S examples/linux -B examples/linux/build
cmake --build examples/linux/build --target embedded-react-desktop
examples/linux/build/embedded-react-desktop # runs the container in the slot

The build copies dist/app.erpkg into the slot next to the executable. Pass a path to run a specific .erpkg, .qbc bytecode blob or .js source instead. With no config, the window shows a "No config loaded" panel and stays up, like firmware would. The mouse is forwarded as touch; ESC quits.

To try a new UI, drop a fresh app.erpkg next to the executable and restart it, which is the desktop version of writing the config partition. For an edit-and-see loop use a simulator instead; see Hot reload.

The host is factored into host.c, a thin layer over the portable er_runtime core: it creates QuickJS, installs the NativeUI bridge and the host globals (screen, console, timers), loads the container and runs the frame loop (pump, commit, present). Read it to see the complete Flow A host in one file.

Flow B: examples/linux-aot​

The same window, with the app compiled ahead of time and no QuickJS in the binary. Build a package, then build the host and run it:

cd packages/embedded-react && npm run aot -- thermostat # → dist/app.erpkg
cd ../..
cmake -S examples/linux-aot -B examples/linux-aot/build
cmake --build examples/linux-aot/build --target embedded-react-desktop-aot
examples/linux-aot/build/embedded-react-desktop-aot # or pass a package's path

The window is 800×480 unless ER_AOT_SCREEN_W and ER_AOT_SCREEN_H say otherwise, and its size picks the package's layout. The host reads the package when it starts, so changing the app needs no rebuild. main.c is the reference Flow B host: register the backend, call erb_load() once, then loop over input, er_commit(), present and tick. The CYD and RP2040 firmware follow the same shape.

Why keep a desktop host​

  • It runs under a debugger. The SDL simulator (npm run sim) is the maintainers' engine-debug tool because gdb and lldb work on it; the browser simulator, being WebAssembly, is the consumer's.
  • Parity. The repository's parity harness renders the same app through both flows on the desktop and diffs the pixels, which is how "a build flag, not a rewrite" is kept true.
  • Tests. The engine's CTest suites and the bridge's runtime tests are host-side builds of the same code that ships to the boards.