gemba
Version, currently main branch2 versions
- 0.1.0latestSep 14, 2026
- main branchSep 11, 2026
github.com/jamescook/gemba
GBA emulator frontend - a Crystal/Tryst port of the ruby gemba app (teek + libmgba).
Installation
# Add this to your shard.yml
dependencies:
gemba:
github: jamescook/gemba
branch: mainmain is a branch, not a release, so this tracks it rather than pinning a version.
Then run:
shards installshard.yml
- Crystal
>= 1.21.0- License
- MIT
- Author
- James Cook
- Target
gembafrom src/gemba_app.cr
Dependencies
Runtime Dependencies
- tryst*github: jamescook/tryst, branch: main
- tryst-sdl*github: jamescook/tryst-sdl, branch: main
- tryst-switch*github: jamescook/tryst-switch, branch: main
- tryst-segmented*github: jamescook/tryst-segmented, branch: main
- tryst-value-slider*github: jamescook/tryst-value-slider, branch: main
Development Dependencies
- ameba*github: crystal-ameba/ameba, tag: v1.7.0dev
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
gemba
A Crystal/Tryst port of gemba, a GBA emulator frontend (ruby original:
teek + libmgba).
Its main job is to be a real, non-trivial application built on Tryst - the kind of thing that finds the rough edges a widget-by-widget test suite never will: real concurrency (a full emulation loop running alongside Tk's own event loop), real file I/O, a real native CDN fetch, a settings UI, modals, hotkeys, all of it. Every gap it exposes in Tryst gets fixed in Tryst, not worked around here. A working, genuinely playable GBA frontend is very much the goal too - just the second one.
Tryst, tryst-sdl and the tryst widget shards each live in their own
repo. gemba follows their main branches rather than their releases,
and shard.lock pins the exact commits it was last tested against - see
Dependencies.
Screenshots

Installing
Homebrew (macOS)
brew install jamescook/tap/gemba
This builds from source, including libmgba and rcheevos, and pulls in SDL3, Tcl/Tk and the rest as Homebrew dependencies. The formula lives in jamescook/homebrew-tap.
From source
Needs a working host build first - see Developing. Then:
make # release build -> bin/release/gemba
make install # -> ~/.local/bin/gemba + ~/.local/share/gemba
make && sudo make install PREFIX=/usr/local # system-wide
make DESTDIR=/tmp/stage install # staged, for a formula or .deb
make uninstall
make install only rebuilds when a source file is newer than the
release binary, so the sudo half of the system-wide line just copies -
no second release build running as root. The release build lands in
bin/release/, not bin/, so a debug shards build never gets
installed by mistake.
PREFIX defaults to ~/.local - no sudo, and it is the XDG/systemd
user convention most modern Linux distros put on PATH when it exists
(Debian/Ubuntu's stock ~/.profile does). macOS does not, so make install prints a hint if the target bin dir is missing from PATH.
/usr/local is the system-wide choice on both: it is in /etc/paths on
every Mac, and on Apple Silicon it is not Homebrew's prefix
(/opt/homebrew), so it collides with nothing brew owns. Under sudo,
pass PREFIX explicitly - sudo make install alone installs root-owned
files into your own ~/.local.
The install writes two things, and both are needed:
$PREFIX/bin/gemba
$PREFIX/share/gemba/{assets,data,locales}
The binary finds that second half through ../share/gemba relative to
itself, so the pair relocates anywhere as a unit without the prefix
being compiled in. gemba config prints which one it resolved:
Data: /usr/local/share/gemba (installed)
Data: /path/to/checkout (source tree)
A build run straight out of a checkout (crystal run, or shards build
with no make install) has no share/ beside its binary and reads the
source tree instead - which is why the second line exists and why a
checkout needs no install to be developed in.
The binary links Homebrew's dylibs by absolute path
(/opt/homebrew/opt/{sdl3,sdl3_ttf,tcl-tk,...}), so a copy built this
way and handed to someone else only runs if they have the same formulae
installed. Point them at the Homebrew install instead.
Developing
Needs whatever tryst and tryst-sdl need (Crystal >= 1.21.0, Tcl/Tk 8.6 or 9, SDL3), plus libmgba and rcheevos - see tryst-sdl's own README for per-platform SDL package names.
Host build
A host build needs three vendored artifacts that git does not track and nothing builds automatically - do this once before your first host build. The Dockerfile and the Homebrew formula run the identical recipe, so keep all three in sync if the versions or flags change.
# 1. libmgba, built minimal (no Qt/SDL frontend, no GL).
mkdir -p vendor
git clone --depth 1 --branch 0.10.5 https://github.com/mgba-emu/mgba.git vendor/mgba
cmake -S vendor/mgba -B vendor/build \
-DMARKDOWN= \
-DBUILD_SHARED=OFF -DBUILD_STATIC=ON \
-DBUILD_QT=OFF -DBUILD_SDL=OFF \
-DBUILD_GL=OFF -DBUILD_GLES2=OFF -DBUILD_GLES3=OFF \
-DBUILD_LIBRETRO=OFF -DSKIP_FRONTEND=ON \
-DUSE_SQLITE3=OFF -DUSE_ELF=OFF -DUSE_LZMA=OFF -DUSE_EDITLINE=OFF -DUSE_FFMPEG=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX="$(pwd)/vendor/mgba-install" \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build vendor/build -j "$(getconf _NPROCESSORS_ONLN)"
cmake --install vendor/build
rm -rf vendor/mgba vendor/build
# 2. rcheevos, pinned to the commit ruby gemba's own vendor/rcheevos
# submodule uses.
git clone --quiet https://github.com/RetroAchievements/rcheevos.git vendor/rcheevos
git -C vendor/rcheevos checkout --quiet e9ca3694c862b61235595176dac4b22677848c93
scripts/build_rcheevos.sh
rm -rf vendor/rcheevos
# 3. native/null_logger.o - real C (a genuine va_list parameter Crystal
# can't express), built against libmgba's just-installed headers.
cc -c -I vendor/mgba-install/include native/null_logger.c -o native/null_logger.o
# 4. The Crystal dependencies, at the commits in shard.lock.
shards install
vendor/ and native/*.o are both gitignored - this is a local build
step, not something to commit. Re-run it whenever vendor/mgba-install,
vendor/rcheevos-build, or native/null_logger.o go missing (e.g.
after a clean checkout).
Tests
scripts/docker-test.sh # full suite, Debian forky
scripts/docker-test.sh spec/gemba_spec.cr # one file
Run specs in Docker, not with crystal spec on the host: the suite
opens real Tk windows, which steal focus while it runs. Docker builds
libmgba and rcheevos itself, so it needs none of the host setup above.
Dependencies
Every tryst-family dependency tracks branch: main, and
shard.override.yml forces tryst and tryst-vector to main everywhere
in the dependency graph. shard.lock is committed, so shards install,
the Docker image and the Homebrew formula all build the same commits. To
pick up new upstream commits, run shards update, run the full Docker
suite, and commit the updated lock.
Releasing
-
Bump the version in
shard.yml,src/gemba.crandspec/gemba_spec.cr. -
If the release should include newer tryst commits,
shards update. -
scripts/docker-test.sh- the full suite must pass. -
Add a
CHANGELOG.mdentry dated today, commit, then tag and push:git tag vX.Y.Z && git push origin main vX.Y.Z. -
In jamescook/homebrew-tap, point
Formula/gemba.rbat the new tag's tarball and update its checksum:curl -fsSL https://github.com/jamescook/gemba/archive/refs/tags/vX.Y.Z.tar.gz | shasum -a 256Then
brew install --build-from-source jamescook/tap/gemba,brew test jamescook/tap/gembaandbrew audit --strict jamescook/tap/gemba, and push.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This branch
- Branch
main- Seen
- Sep 11, 2026
- Crystal
>= 1.21.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/jamescook/gemba
Metadata
- Created
- Aug 29, 2026
- Updated
- Sep 23, 2026
- Synced
- Sep 22, 2026
- Versions
- 2