An application framework for creating small portable apps. Designed for the keygen and media player era of app design. Written in ANSI-C with the // extension. Supported backends may change over time but the initial targets are: Win32 and Linux.
  • C 95.9%
  • Java 2.7%
  • Shell 0.6%
  • Makefile 0.4%
  • Python 0.2%
Find a file
xo corvus 67cd10acfd
Some checks failed
/ linux release (push) Successful in 20s
/ macos, resampler check (push) Successful in 48s
/ linux debug, tests (push) Successful in 1m27s
/ windows release (push) Successful in 12s
/ windows debug, tests under wine (push) Successful in 1m25s
/ android build (push) Successful in 15s
/ tests on the lab phones (push) Failing after 4m55s
serve: logs where a source gave up, tests that a short one ends the connection
2026-11-13 08:46:32 -08:00
.forgejo phones job without the staged binary 2026-11-03 22:30:53 -08:00
assets fallback fonts for the scripts a font lacks, noto's built in 2026-10-18 09:23:21 -07:00
src serve: logs where a source gave up, tests that a short one ends the connection 2026-11-13 08:46:32 -08:00
test serve: logs where a source gave up, tests that a short one ends the connection 2026-11-13 08:46:32 -08:00
tools resampler check on the mac, phone lock, unit tests on the lab phones 2026-11-03 18:52:12 -08:00
.clang-format initial commit 2026-10-06 12:14:27 -07:00
.gitattributes fallback fonts for the scripts a font lacks, noto's built in 2026-10-18 09:23:21 -07:00
.gitignore initial commit 2026-10-06 12:14:27 -07:00
android-tools.txt android platform and build-tools pinned 2026-10-12 11:29:44 -07:00
API.md xa_window_set_keep_awake, on Android and Windows 2026-11-11 16:26:26 -08:00
build-darwin.bash resampler check on the mac, phone lock, unit tests on the lab phones 2026-11-03 18:52:12 -08:00
build-linux.bash xa_file_replace keeps the mode and refuses hard links, xa_path_resolve 2026-10-20 20:18:11 -07:00
build-win32.cmd build-win32.cmd stops on a test run that crashed 2026-10-20 13:08:04 -07:00
LICENSE MPL 2.0 2026-10-12 11:24:33 -07:00
LICENSE-lucide.txt SVG loader and xa_svg_draw, with a cache and a few built-in icons 2026-10-17 15:51:36 -07:00
LICENSE-noto.txt fallback fonts for the scripts a font lacks, noto's built in 2026-10-18 09:23:21 -07:00
LICENSE-selawik.txt menus are drawn on every platform, in the standard look 2026-10-17 16:40:34 -07:00
README.md resampler check on the mac, phone lock, unit tests on the lab phones 2026-11-03 18:52:12 -08:00

xo-app

A C89 application framework for small cross-platform apps: media players, emulators, notes, calculators, small tools. Win32 and X11 first, and Android. Software renderer first, OpenGL second. Custom window chromes, winamp style, and DPI scaling done properly.

Layout

  • src/ - the library. src/inc/xo-app/ is the API, and the contracts are in the headers' comments. API.md has what they all assume and a small app to start from.
  • src/vendor/ - stb, dr_libs and the Speex resampler. UPSTREAM says where each came from and what's patched.
  • src/java/ - the Android side: the activity, the playback service, the media session and the browse service.
  • test/ - unit tests, test/desktop/ for the ones that open real windows, test/manual.md for what has to be checked by hand.
  • tools/ - bake, which the build runs, and inspect, compare-renderers, backdrop and grab. gallery shows every UI part in the standard look, light and dark. sign makes the Ed25519 keys and signatures xa_sign_verify checks; sign alone prints how. subset_fallback.py cuts the fallback fonts in assets/ down from Noto's own files, with uv run; its top says where those come from.

Building

clang only, no MSVC. C89 with // comments: -std=c89 -pedantic -Wno-comment.

  • Windows: build-win32.cmd. Builds against the MSYS2 mingw64 sysroot, XA_SYSROOT, C:/msys64/mingw64 unless it's set, with its mingw32-make.
  • Linux: build-linux.bash. Needs lld, llvm-ar and the X11, Xext, Xcursor, GL and dbus-1 development files, and Xtst for the desktop tests. win32 in its arguments cross-builds for Windows with llvm-mingw's clang first on PATH, XA_SYSROOT set to its x86_64-w64-mingw32 directory, and runs what it built under Wine.
  • Android: android in either script's arguments, x86_64 with it for the emulator instead of arm64-v8a. What it wants is in android-tools.txt.
  • macOS: the library doesn't build yet. build-darwin.bash resample builds and runs the SIMD resampler check (tools/resample_check.c), which is what the Mac runner does.

Both scripts compile in parallel, a job to each CPU. --serial in the arguments, or XA_JOBS=1 in the environment, builds one file at a time; XA_JOBS=N sets the count.

Output goes to out/<platform>-<config>/.

argument
debug The default.
release Built for size, LTO.
test Builds and runs the tests. On Linux, that's test, asan, tsan and test-release one after another. On Android it runs them through adb.
test-release The tests, built as release is.
asan, tsan Linux only. The tests under the address and undefined, or the thread, sanitizer.
desktop The desktop tests and compare-renderers. On Linux it wants xvfb-desktop on PATH; .forgejo/ci-image has one.
desktop-asan Linux only. The desktop tests under asan.
pulse Linux only. The audio device tests against the PulseAudio server. They add two null sinks and move the default sink about while they run.
coverage Linux only. A coverage report of the unit and desktop tests.
format, format-check clang-format over the code, or check it's formatted. Pinned to clang-format 23; CLANG_FORMAT names another binary.
check Fails if anything in src/ but memory.c uses the app's scratch arena.
profile Windows only, with a config: release profile, test profile. A build for Superluminal, into out/win32-<config>-profile/. Below.
clean Deletes out/.

XA_TEST_ARGS is passed to the test runners.

Profiling

profile builds for Superluminal. It reads symbols from a PDB, and clang and lld make one beside each exe from the mingw build as it is, no MSVC needed. Frame pointers are kept and nothing is stripped; the config's optimisation stays.

With Superluminal's API there, in C:\Program Files\Superluminal\Performance\API or wherever SUPERLUMINAL_API says, the marks in xa_profile.h reach it too: named stretches of the frame, rendering, decoding, workers and the rest, and the threads by name. Without it the build says so, the marks are nothing, and the output goes to out/win32-<config>-profile-symbols/ instead. The exe loads the API's DLL when it starts, from where the build found it or beside the exe, and runs without it; the log says which.

src/profile.mk has the flags, and an app built against xo-app includes it to build alike. Apps use the XA_PROFILE_* macros and nothing of Superluminal's.

CI

.forgejo/workflows/ci.yml runs on every push: on Linux the tests, the desktop tests and format-check, and a release build; on Windows, cross-built with llvm-mingw and run under Wine, the tests, the desktop tests and a release build; and for Android, the library and the tests built for both ABIs but not run. Its container images are built from .forgejo/ci-image.

Two jobs run on vp03, the Mac, which is where the lab phones are plugged in. macos runs the resampler check, the NEON loops against speex's C on Apple Silicon with every difference a failure, and the phone lock's own tests. phones takes the arm64 test binary the android job built and runs the whole unit suite on every phone attached, through test/phones.bash; no phone, or a phone someone else holds for more than five minutes, is skipped with a message and the job stays green, and only a failing test (or one hanging past 25 minutes) turns it red. The runner is the one labelled macos.

The phone lock

tools/phone-lock.bash is a lock per phone, shared by CI and the agents on vp03. It is a directory ~/xapa/phone-locks/SERIAL with an owner file in it, the same convention the agents' scripts already use (mkdir, write owner, rm it and rmdir at the end), so both kinds work together.

tools/phone-lock.bash take SERIAL --name N --reason R [--wait SEC] [--max SEC]
tools/phone-lock.bash release SERIAL --name N
tools/phone-lock.bash run SERIAL --name N --reason R [--wait SEC] [--max SEC] -- CMD...
tools/phone-lock.bash status [SERIAL...]

--wait is how long to wait for someone else's hold, default none; running out of it is exit status 3. --max is how long a hold may last, default 2400 s (40 minutes); after it, anyone may take the lock. A lock is also taken when the process that held it (on this host) is gone, and an agent's lock with no times in it counts as stale 40 minutes after its owner file was last written. release only removes a lock whose owner is the name given. run holds the lock for the length of a command, lets go on exit, on a signal and when the command fails, and stops the command at the maximum (status 124). test/phone-lock-test.bash shows all of it, in about half a minute.

CI copies the script to ~/xapa/bin/phone-lock on vp03 on each run.

License

Mozilla Public License 2.0, in LICENSE. The vendored files in src/vendor/ keep their own. Lief, the font in assets/, is Lady Liefy's, marked CC0 1.0. Selawik Semilight and Selawik Light, beside it and baked in as the standard look's fonts, are Microsoft's, under the SIL Open Font License 1.1, in LICENSE-selawik.txt. Noto Sans Light and Noto Sans CJK JP Light, cut down and baked in for xa_font_add_builtin_fallback, are the Noto Project Authors' and Adobe's, under the same licence, in LICENSE-noto.txt. The built-in icons in src/svg_icons.c are Lucide's, ISC, in LICENSE-lucide.txt.