This guide covers building LessUI, running tests, and contributing code.
LessUI uses Docker to cross-compile for ARM devices. You don't need the actual hardware to build.
- Docker Desktop
- Make (pre-installed on macOS/Linux)
- Git
Build everything:
makeBuild for one platform:
make PLATFORM=miyoominiAvailable platforms: miyoomini, my282, my355, trimuismart, rg35xx, rg35xxplus, rgb30, tg5040, m17, magicmini, zero28
Deprecated platforms: gkdpixel (no longer supported)
For rapid UI development on macOS, build and run natively without Docker:
# First-time setup: Install SDL2 libraries
brew install sdl2 sdl2_image sdl2_ttf
# Build and run launcher
make dev-runThis gives you:
- Instant builds (native compiler, no Docker overhead)
- Live debugging with AddressSanitizer
- Visual testing in SDL2 window (640×480 or 854×480)
- Keyboard controls: Arrow keys (D-pad), A/S/W/Q (buttons), Enter (Start), Space (Menu)
- Quit: Hold Backspace/Delete
The fake SD card lives at workspace/desktop/FAKESD/. Add test ROMs there:
mkdir -p workspace/desktop/FAKESD/Roms/GB
cp ~/Downloads/game.gb workspace/desktop/FAKESD/Roms/GB/Development commands:
make dev # Build launcher for macOS
make dev-run # Build and run launcher
make dev-clean # Clean macOS build artifactsLimitations:
- macOS platform is for launcher development only
- Cannot test libretro cores (player) - use actual hardware
- Hardware features stubbed (brightness, volume, power)
Drop into a build environment for interactive development:
make PLATFORM=miyoomini shellInside the container you can build components individually:
cd /root/workspace/all/launcher
makemake test # Run unit tests
make lint # Static analysis
make format # Auto-format codeFind bugs before they ship:
make lint # Check common codeThis runs clang-tidy on workspace/all/ which contains all the platform-independent code.
LessUI uses Unity for testing:
make testTests live in tests/unit/ and mirror the structure of workspace/all/. For example, workspace/all/common/utils.c has tests in tests/unit/all/common/test_utils.c.
Here's an example test:
#include "unity.h"
#include "utils.h"
void test_getDisplayName_stripsExtension(void) {
char result[256];
getDisplayName("game.gb", result);
TEST_ASSERT_EQUAL_STRING("game", result);
}Add your test function to the main() function in your test file:
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_getDisplayName_stripsExtension);
return UNITY_END();
}Run tests: make test
LessUI uses clang-format with tabs and K&R-style braces:
make format # Format all code
make format-check # Check without changingSettings in .clang-format:
- Tabs for indentation (width: 4)
- Opening braces on same line
- Left-aligned pointers (
char* name) - 100 character line limit
Check shell scripts with shellcheck:
make lint-shellLessUI/
├── workspace/
│ ├── all/ # Platform-independent code
│ │ ├── launcher/ # Launcher
│ │ ├── player/ # Libretro frontend
│ │ └── common/ # Shared utilities and API
│ │
│ └── <platform>/ # Platform-specific code
│ ├── platform/ # Hardware definitions
│ ├── keymon/ # Button monitoring daemon
│ └── libmsettings/ # Settings library
│
├── skeleton/ # Files copied to SD card
│ ├── SYSTEM/res/ # Shared assets (fonts, sprites)
│ └── BOOT/ # Boot scripts
│
├── build/ # Build output (generated)
├── tests/ # Unit tests
└── docs/ # Documentation
LessUI uses a platform abstraction layer so one codebase supports 20+ devices:
Common code (workspace/all/) calls abstract APIs like:
GFX_clear()- Clear screenPAD_poll()- Read buttonsPWR_getBatteryLevel()- Get battery %
Platform code (workspace/<platform>/platform/) implements these for specific hardware:
platform.h- Hardware constants (screen size, button codes)platform.c- Hardware functions (if needed)
Example from platform.h:
#define FIXED_WIDTH 640
#define FIXED_HEIGHT 480
#define BUTTON_A SDLK_SPACEThe common code compiles once, then links with different platform definitions for each device.
- Edit files in
workspace/all/ - Write tests in
tests/unit/ - Run
make testto verify - Format with
make format - Check with
make lint
- Edit
workspace/<platform>/platform/files - Test on actual hardware (cross-compilation can't verify hardware behavior)
- Document in
workspace/<platform>/README.md
Build for a specific platform:
make PLATFORM=miyoomini buildOutput goes to build/ directory. Copy to SD card and test on device.
- Run all checks:
make test lint format-
Test on at least one platform if possible
-
Update documentation:
- Platform README if hardware-specific
- CLAUDE.md if changing architecture
- This file if changing workflows
Keep it simple:
Add battery percentage to status bar
Show battery % next to icon when available.
Fallback to icon-only on platforms without %.
First line is summary (imperative mood). Body explains why and what changed.
Cores are built externally in the LessUI-Cores repository. To add a core to LessUI:
- Add core to LessUI-Cores repository (core build system)
- Add core configuration to
workspace/all/paks/Emus/cores.json - Create core config file
workspace/all/paks/Emus/configs/base/<core>/default.cfg - Run
./scripts/generate-paks.sh allto generate platform paks - Test on target hardware
See cores.md for details on how cores are distributed and loaded. See paks-architecture.md for pak generation system documentation.
- Write a failing test first (if possible)
- Fix the bug
- Verify test passes
- Run
make lintto check for issues
LessUI uses a source + generated asset system for easy maintenance.
Source assets live in skeleton/SYSTEM/res-src/:
assets.png- UI sprite sheet (512×512)installing.png,updating.png,bootlogo.png,charging.png- Boot screens (640×480)
To update assets:
# 1. Edit source file in skeleton/SYSTEM/res-src/
# 2. Regenerate all variants:
./scripts/generate-assets.sh
# 3. Commit both source and generated filesThis generates 40+ variants (PNGs at different scales, BMPs for different platforms) automatically.
See skeleton/SYSTEM/res-src/README.md for detailed asset guidelines and ideal resolutions.
Check Docker Desktop is running. Restart if needed.
Try cleaning:
make cleanThen rebuild:
make PLATFORM=miyoominiTests run in Docker. Check test output carefully. Tests are strict about memory and undefined behavior.
Common issues:
- Buffer too small
- Uninitialized variables
- Off-by-one errors
Check workspace/<platform>/install/boot.sh for errors. Boot scripts are platform-specific and finicky.
Enable verbose logging by editing boot script to redirect output:
./launcher.elf > /tmp/launcher.log 2>&1Then check logs on device.
- Architecture Guide - How LessUI works internally
- Cores Guide - How libretro cores work
- Pak Development - Creating custom emulator paks
- Platform READMEs - Platform-specific docs
- Main Project Docs - Comprehensive reference
- Check existing code for examples
- Platform READMEs document hardware quirks
- CLAUDE.md has detailed architecture notes
- GitHub issues for bugs and questions