Skip to content

Development Setup

Development environment setup, build processes, and workflows for HelixScreen.

Terminal window
# Install dependencies (see platform-specific below)
npm install && make venv-setup
# Build and run
make -j
./build/bin/helix-screen --test -vv # Mock printer + DEBUG logs
Terminal window
brew install cmake bear imagemagick python3 node shellcheck bats-core
npm install # lv_font_conv and lv_img_conv
make venv-setup # Python venv with pypng/lz4

Minimum: macOS 10.15 (Catalina) for CoreWLAN/CoreLocation WiFi APIs.

Terminal window
sudo apt install cmake bear imagemagick python3 python3-venv clang make npm \
shellcheck bats libnl-3-dev libnl-genl-3-dev libssl-dev
npm install && make venv-setup
Terminal window
sudo dnf install cmake bear ImageMagick python3 clang make npm \
ShellCheck bats libnl3-devel openssl-devel
npm install && make venv-setup
CategoryComponentsNotes
Requiredclang, cmake 3.16+, make, python3, node/npmCore build tools
Auto-builtSDL2, spdlog, libhvBuilt from submodules if not system-installed
Always submodulelvglProject-specific patches required
Optionalbear, imagemagick, shellcheck, bats-coreIDE support, screenshots, shell linting/testing
Terminal window
make check-deps # Check what's missing
make install-deps # Auto-install (interactive)
Terminal window
make -j # Parallel incremental build (recommended)
make build # Clean parallel build with progress/timing
make clean && make -j # Full rebuild (only when needed)
make V=1 # Verbose mode (shows full commands)
make compile_commands # Generate compile_commands.json for IDE/LSP
Terminal window
./build/bin/helix-screen # Production mode
./build/bin/helix-screen --test # Full mock mode (no hardware)
./build/bin/helix-screen --test --real-wifi # Mix real WiFi + mock printer
./build/bin/helix-screen --dark # Force dark theme
./build/bin/helix-screen --light # Force light theme
./build/bin/helix-screen -d 1 -s small # Display 1, small size
FlagEffect
--testEnable test mode (required for mocks)
--real-wifiUse real WiFi instead of mock
--real-ethernetUse real Ethernet instead of mock
--real-moonrakerConnect to real printer
--real-filesUse real printer files

Test mode keyboard shortcuts: S=screenshot, P=test prompt, N=test notification, Q/Esc=quit

FlagEffect
-w, --wizardForce the first-run configuration wizard
--wizard-step <step>Jump to a specific wizard step (0-12) for testing

--wizard is destructive to wizard-managed config — it is meant to fully re-run setup, not just re-open it. On startup with --wizard, the app clears all wizard-managed state in settings.json so a stale or wrong install-time seed is recoverable:

  • Clears the preset marker via Config::clear_preset() (erases the top-level "preset" key). Because has_preset() is then false, the re-run becomes a full wizard — the identify and hardware pages reappear instead of being skipped.
  • Clears the active printer’s moonraker_host (set to ""), so the connection step is re-entered.
  • Clears the cached hardware snapshot and the wizard’s heater/sensor/fan/LED/filament-sensor selections, so stale hardware choices don’t trigger false hardware-health warnings.

This is the recovery path when an install-time auto-seed (see INSTALLER.md) picked the wrong printer: re-run with --wizard to drop the seed and reconfigure from scratch.

A headless one-shot that queries a running Moonraker over its REST API, runs the same detection logic the wizard uses, prints a JSON verdict to stdout, and exits. No UI is started. Useful for debugging which preset a given printer matches, or for scripting install-time detection.

FlagArgumentDefaultEffect
--detect-printer(none)Run headless detection, print JSON, exit
--host <addr>host/IP127.0.0.1Moonraker host to query
--port <n>1-655357125Moonraker port to query
Terminal window
# Detect the local printer
./build/bin/helix-screen --detect-printer
# Detect a printer on another host
./build/bin/helix-screen --detect-printer --host 192.168.1.74 --port 7125

Internally it does three REST GETs against http://HOST:PORT (3-second timeout each): /printer/objects/list, /printer/info (hostname), and /printer/objects/query?configfile=settings (kinematics + build volume from the stepper_x/y/z config). The resulting hardware profile is fed to PrinterDetector::auto_detect().

Exit codes: 0 on success (verdict printed). 1 if Moonraker’s object list is unavailable at the given host/port (a warning is logged; no JSON is printed).

JSON output shape — one compact line, terminated by a newline:

{"model":"Creality K1 Max","preset":"k1_max","confidence":92,"runner_up_preset":"qidi_q2","runner_up_confidence":43}
KeyTypeMeaning
modelstringHuman-readable detected printer name (PrinterDetectionResult::type_name)
presetstring or nullPlatform preset id for the match; null when the result has no preset
confidenceinteger (0-100)Detection confidence for the top match
runner_up_presetstring or nullPreset id of the second-place candidate; null when there is none
runner_up_confidenceinteger (0-100)Confidence score of the runner-up

When there is no credible second candidate, the runner-up fields collapse:

{"model":"FlashForge AD5M","preset":"ad5m","confidence":88,"runner_up_preset":null,"runner_up_confidence":0}

The installer’s Tier-2 detection (see INSTALLER.md) parses exactly this output and applies its confidence gate to confidence and the confidence - runner_up_confidence margin.

For cross-compilation, patches, and advanced options, see BUILD_SYSTEM.md.

LevelFlagUse For
WARN(default)Errors and warnings only
INFO-vUser-visible milestones
DEBUG-vvTroubleshooting, summaries
TRACE-vvvPer-item loops, wire protocol
Terminal window
./build/bin/helix-screen --log-dest=console # Console (default on macOS)
./build/bin/helix-screen --log-dest=journal # systemd journal (Linux)
./build/bin/helix-screen --log-dest=file --log-file=/tmp/helix.log

Viewing logs on Linux:

Terminal window
journalctl -t helix -f # systemd
tail -f /var/log/helix-screen.log # file

ALWAYS use spdlog - never printf/cout/LV_LOG_*:

spdlog::info("[ComponentName] Message: {}", value);
spdlog::debug("[Theme] Registered {} items", count);

spdlog submodule: Uses fmt-11.2.0 branch. Initialize with git submodule update --init --recursive.

Terminal window
cp config/settings.json.template config/settings.json # First-time setup
  • config/settings.json - User settings (git-ignored)
  • config/settings.json.template - Defaults (versioned)

Never commit user config. Legacy root location auto-migrates.

{
"printer": {
"heaters": { "bed": "heater_bed", "hotend": "extruder" },
"temp_sensors": { "bed": "heater_bed", "hotend": "extruder" },
"fans": { "part": "fan", "hotend": "heater_fan hotend_fan" }
}
}

Naming: Container keys plural (heaters), role keys singular (bed).

LVGL scales UI based on DPI. Default: 160 (reference, no scaling).

Terminal window
./build/bin/helix-screen --dpi 170 # 7" @ 1024x600 (BTT Pad 7)
./build/bin/helix-screen --dpi 187 # 5" @ 800x480
./build/bin/helix-screen --dpi 201 # 4.3" @ 720x480 (AD5M)
HardwareResolutionDPI
Reference160
7” LCD1024×600170
5” LCD800×480187
AD5M720×480201
Terminal window
./build/bin/helix-screen --display 1 # Secondary display
./build/bin/helix-screen -d 1 -s small # Combined options

Uses SDL_GetDisplayBounds() for proper positioning on multi-monitor setups.

Terminal window
# Interactive: Press 'S' in running UI
# Automated:
./scripts/screenshot.sh helix-screen output-name [panel] [options]
./scripts/screenshot.sh helix-screen home-screen home
./scripts/screenshot.sh helix-screen motion motion -s small
# Environment overrides:
HELIX_SCREENSHOT_DISPLAY=0 ./scripts/screenshot.sh helix-screen test home
HELIX_SCREENSHOT_OPEN=1 ./scripts/screenshot.sh helix-screen review home

Output: /tmp/ui-screenshot-[name].png

Terminal window
python3 scripts/generate-icon-consts.py # After editing include/ui_fonts.h
make icon # Generate platform icons

See BUILD_SYSTEM.md for complete font generation details.

Terminal window
make compile_commands # Generates compile_commands.json (requires bear)

VS Code: C/C++ extension + clangd extension Vim/Neovim: Configure clangd LSP client CLion: Import as Makefile project

  1. Edit code in src/ or include/
  2. Edit XML in ui_xml/no rebuild needed (use hot reload or just relaunch)
  3. Build with make -j (only when C++ changes)
  4. Test with ./build/bin/helix-screen --test -vv [panel]
  5. Screenshot with S key or ./scripts/screenshot.sh
  6. Commit working incremental changes
Change TypeLocationRebuild?Hot Reload?
Layout, styling, colorsui_xml/*.xmlNoYes — auto-detected
Logic, bindings, handlerssrc/*.cpp, include/*.hYes (make -j)No
Theme colorsconfig/themes/*.jsonNo — just restartNo
Translationsconfig/strings/*.yamlYes (code generation step)No

For the fastest UI iteration, use hot reload — edit XML, save, see updates without restarting:

Terminal window
HELIX_HOT_RELOAD=1 ./build/bin/helix-screen --test -vv

When enabled, a background thread watches all XML files for changes and re-registers modified components automatically. After a file changes, navigate away from the panel and back to see the new layout. See HELIX_HOT_RELOAD for details and limitations.


For layout work, styling fixes, and alternate screen layouts, the UI Contributor Guide is the primary reference. It covers breakpoints, design tokens, pre-themed widgets, layout overrides, and what needs work.

Key points for UI contributors:

  • XML layouts load at runtime — no rebuild needed for layout/styling changes. Use HELIX_HOT_RELOAD=1 for live editing without restarting
  • Design tokens are mandatory — use #space_md, #card_bg, <text_body> instead of hardcoded values
  • 5 breakpoint tiers based on screen height: tiny (≤390px), small (391–460px), medium (461–550px), large (551–700px), xlarge (>700px)
  • Layout overrides let you provide alternate XML for ultrawide, portrait, or tiny screens without touching the standard layouts
  • Test at multiple sizes with -s WIDTHxHEIGHT:
    Terminal window
    ./build/bin/helix-screen --test -vv -s 480x320 # Tiny
    ./build/bin/helix-screen --test -vv -s 800x480 # Standard
    ./build/bin/helix-screen --test -vv -s 1920x480 --layout ultrawide
PathContents
ui_xml/All XML layouts (~170 files)
ui_xml/components/Reusable XML components
ui_xml/ultrawide/Ultrawide layout overrides
ui_xml/globals.xmlDesign tokens and global variables (shared, never override)
config/themes/Theme JSON files (color palettes)

For major feature work, use git worktrees to isolate your changes:

Terminal window
scripts/setup-worktree.sh feature/my-branch # Creates in .worktrees/

This creates a worktree with symlinked dependencies and a ready-to-build environment. Worktrees keep main clean while you experiment.


Real WiFi scanning requires Location Services (network SSIDs reveal location).

Easiest: System Settings → Privacy & Security → Location Services → Enable Terminal

Without permission, app falls back to mock WiFi. Check with:

Terminal window
./build/bin/helix-screen --wizard -vv 2>&1 | grep -i "location\|wifi"
Terminal window
make check-deps # Check missing dependencies
make install-deps # Auto-install
make clean && make V=1 # Verbose rebuild

SDL2 not found: brew install sdl2 (macOS) or sudo apt install libsdl2-dev (Linux)

For complete troubleshooting, see BUILD_SYSTEM.md.


Terminal window
make setup # Configures pre-commit hook + commit template

The pre-commit hook auto-formats code (clang-format) and runs quality checks.

Class-based architecture required for all new code:

// ✅ CORRECT: Class-based panel
class MotionPanel : public PanelBase {
public:
explicit MotionPanel(lv_obj_t* parent);
void show() override;
};
// ❌ AVOID: Function-based (legacy)
void ui_panel_motion_init(lv_obj_t* parent);

Naming conventions:

  • Functions/variables: snake_case (ui_panel_home_init, temp_target)
  • XML files: kebab-case (nozzle-temp-panel.xml)
  • Constants: UPPER_SNAKE_CASE (MAX_TEMP)

Critical patterns:

// Widget lookup: use names, not indices
lv_obj_t* label = lv_obj_find_by_name(panel, "temp_display"); // ✅
lv_obj_t* label = lv_obj_get_child(panel, 3); // ❌
// LVGL API: public only
lv_obj_get_x(); // ✅ Public
_lv_obj_mark_dirty(); // ❌ Private (underscore prefix)

Copyright headers (all new files):

GPL-3.0-or-later
// Copyright 2025 356C LLC
type(scope): description
Optional detailed explanation.

Types: feat, fix, docs, style, refactor, test, chore

Examples:

feat(ui): add temperature control overlay panel
fix(build): resolve SDL2 linking on macOS Sequoia
docs(readme): update build instructions

Before submitting:

  1. Rebase on latest main
  2. Test build and runtime
  3. Update docs if patterns changed
  4. Add screenshots for UI changes

PR description includes:

  • What changed (summary)
  • Why (context/problem solved)
  • How to test
  • Screenshots (if visual)
  • Breaking changes (if any)
  • Architecture compliance (XML/Subject patterns)
  • Error handling (logging, null checks)
  • Performance (no blocking in UI thread)
  • Documentation updated

The installation system is modular for maintainability and BusyBox compatibility.

scripts/
├── install.sh # Auto-generated for curl|sh (user-facing)
├── install-dev.sh # Modular version (requires lib/installer/)
├── uninstall.sh # Standalone uninstaller
├── lib/installer/ # Shared modules
│ ├── common.sh # Logging, colors, error handling
│ ├── platform.sh # Platform/firmware detection
│ ├── permissions.sh # Root/sudo handling
│ ├── requirements.sh # Pre-flight checks
│ ├── forgex.sh # ForgeX-specific functions
│ ├── competing_uis.sh # Stop GuppyScreen, KlipperScreen, etc.
│ ├── release.sh # Download and extract
│ ├── service.sh # Systemd/SysV service management
│ └── uninstall.sh # Uninstall/clean functions
└── bundle-installer.sh # Generate install.sh from modules

All modules use POSIX #!/bin/sh (not bash) for AD5M’s BusyBox environment:

  • [ ] instead of [[ ]]
  • command -v X >/dev/null 2>&1 instead of &>
  • No arrays (use space-separated strings)
  • ps -ef instead of ps aux
Terminal window
./scripts/bundle-installer.sh -o ./scripts/install.sh

The bundled version inlines all modules for curl|sh usage.

Terminal window
./scripts/install-dev.sh --help # Test modular version (from repo)
./scripts/install.sh --help # Test bundled version (for users)
./scripts/uninstall.sh --help # Test uninstaller
sh -n scripts/install.sh # Check POSIX syntax