← Home

monkeyc and monkeydo: developing Garmin apps on Linux (amd64 and aarch64)

·Jocelyn Stericker

I used my meat hands to type this article.

Bookstride is an app that lets you download audiobooks purchased on Libro.fm and listen to them on your Garmin watch. I made it because I like long runs and Linus Tech Tips had not yet come out with the concept of cargo leggings.

Until today, Bookstride’s device support was way out of date because I switched to Asahi Linux (which helped me start to like computers again) and when I tried to set up Garmin’s SDK I was hit with Ubuntu 22.04-era deps (notably, an old WebKitGTK) and a simulator compiled to amd64. I had given up until yesterday, when I banged my head against the wall for a while and got it to work. Sorry to those with newer watches than me who I’ve neglected for a while!

I’m writing this blog post in the hope that everyone else writing Garmin apps on modern Linux will spend substantially less time getting set up. I’ve included instructions for both amd64 and Asahi.

monkeyc: Compiling and sideloading apps

Monkey C is the language used to write Garmin apps. monkeyc is Garmin’s compiler for Monkey C. monkeyc is written in Java and so runs on both amd64 and aarch64 (e.g., Asahi) natively with no native dependencies. This means you can compile and sideload Garmin apps on modern Linux, Asahi or not, with little fuss.

Installing the Garmin SDK

You can use Garmin’s SDK installer (linked above) to acquire it and the rest of the SDK, but it has the annoying dependencies mentioned earlier (and there’s no aarch64 build). Fortunately, Johan Lindell has created a rather nice headless tool to download Garmin SDKs, which I recommend using instead. This tool is written in go.

Make sure both a Java Development Kit (JDK) and go are installed. For example, on Fedora 44, you can do:

sudo dnf install java-latest-openjdk golang

Then set up the SDK:

mkdir -p ~/.Garmin/ConnectIQ/Sdks
go install github.com/lindell/connect-iq-sdk-manager-cli@latest
~/go/bin/connect-iq-sdk-manager-cli agreement view
~/go/bin/connect-iq-sdk-manager-cli agreement accept
~/go/bin/connect-iq-sdk-manager-cli login
~/go/bin/connect-iq-sdk-manager-cli sdk list
~/go/bin/connect-iq-sdk-manager-cli sdk download 9.2.0
# or whatever version you would like

~/go/bin/connect-iq-sdk-manager-cli sdk set 9.2.0
~/go/bin/connect-iq-sdk-manager-cli sdk current-path

That last line should print something like /home/jocelyn/.Garmin/ConnectIQ/Sdks/connectiq-sdk-lin-9.2.0-2026-06-09-92a1605b2/

You can then install all the metadata needed to compile for devices and simulate them:

~/go/bin/connect-iq-sdk-manager-cli device download --download-all -F

CAUTION

Note that this tool contains a handy -F flag, which stands for “do not segFault”. If you omit it, fonts will not be downloaded, and the simulator will give a helpful message to that effect segfault with an anonymous stack trace that GLM 5.2 can trace back to missing fonts for you.

TIP

Note that the devices metadata can be helpful, e.g., to figure out which of the many, many SKUs to support. I care about devices with CIQ >= 5.0 which support the audio content provider, which I can query with:

find ~/.Garmin/ConnectIQ/Devices/ -name compiler.json | xargs jq -s -r '[ .[] | select( ( (.partNumbers | map(.connectIQVersion | split(".") | map(tonumber)) | max) as $$v | $$v[0] >= 5 ) and ([.appTypes[].type] | index("audioContentProvider"))) | .deviceId ] | sort | .[]'

TIP

You need a key to sign apps, even for sideloading. Garmin has instructions on how to do so:

openssl genrsa -out developer_key.pem 4096
openssl pkcs8 -topk8 -inform PEM -outform DER -in developer_key.pem -out developer_key.der -nocrypt

You can also apparently do this via the vscode extension.

Running monkeyc

If you’re looking for boilerplate, Garmin has some samples, Bookstride is open source, or you can just search for sources with using Toybox.

Once you have an app you’d like to compile, you can use the vscode extension. Alternatively, you can compile it in a single command:

$(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/monkeyc \
  -f monkey.jungle \
  -d fr265s \
  -o build/fr265s.prg \
  -y __REPLACE_THIS_WITH_THE_PATH_TO_YOUR__/developer_key \
  -w

Replace the -y argument with your key, -d with your device, and -o with whatever you want to call your output.

If you want to build for the simulator append _sim to the device ID, e.g., -d fr265s_sim.

You can then plug in your device, and copy it to your device. For example, I open the device with dolphin (requires kio-extras to be installed) and copy the prg file to mtp:/Forerunner 265S/Internal Storage/GARMIN/Apps/. However you copy it, once you unplug your watch it, Should Just Work™.

If you want to upload it to the Connect IQ store, use monekyc’s -e (--package-app) field. This will build for all devices specified in your manifest.

monkeydo: Running apps in the simulator

There are a lot of different devices you may wish to target, so it’s worth trying some in the simulator.

Garmin is funny so their command to run a prg in the simulator is called monkeydo. monkeydo only works when you have a simulator running. The simulator is installed to $(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/simulator. This is amd64 and depends on ancient WebKitGTK. If you don’t have Ubuntu 22.04 or similar, you’ll need to be clever.

TIP

Someone has bundled the simulator into an AppImage. This looks tempting. Unfortunately, on my Fedora 44 system, the WebKitGTK libraries provided immediately segfault when testing an OAuth flow, as it looks for webkit2gtk-4.1, which is too new. The FuseFS (used by AppImage) is also problematic on Asahi/muvm. I did not debug further, because there are easier options!

On amd64: distrobox

If you are running amd64, Distrobox is built exactly for this! It lets you use, for example, Ubuntu 22.04 libraries in a lightly isolated container no matter what you’re actually running. The home folder is bind-mounted to the distro you install.

Install it. For example, on Fedora 44:

sudo dnf install distrobox

Then create an ubuntu 22.04 environment, answering yes to the questions:

distrobox create --image ubuntu:22.04 --name ubuntu-22-04

Install dependencies:

distrobox enter ubuntu-22-04 -- sudo apt install curl unzip libsecret-1-0 libexpat1 libxext6 libwebkit2gtk-4.0-37 libsm6

Then, finally, run it!

distrobox enter ubuntu-22-04 -- $(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/simulator

A window should pop up. Even on macOS, the simulator is a bit crashy. If it fails, maybe try again.

You can then run your app. For example,

$(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/monkeyc \
  -f monkey.jungle \
  -d fr265s_sim \
  -o build/fr265s_sim.prg \
  -y __REPLACE_THIS_WITH_THE_PATH_TO_YOUR__/developer_key \
  -w

$(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/monkeydo \
  $(pwd)/fr265s_sim.prg \
  fr265s

Note the _sim in the device ID, since we are building for the simulator. monkeydo talks to the simulator via a network connection.

On aarch64 (Asahi): muvm

On Asahi, to run the Garmin simulator, we need to emulate amd64 (via FEX) and account for the differing page sizes between Asahi and everything on amd64 (via muvm).

For more on how this works, see AAA Gaming on Asahi Linux and Beyond Gaming: X11 bridging in muvm. It’s incredible stuff, and gaming on Asahi is arguably better than on macOS right now.

I installed Steam before this simulator, but in case you do not like fun, I think the following is sufficient for the simulator:

sudo dnf install fex-emu

You then need to download an ero (read-only image) for Ubuntu 22.04.

curl https://rootfs.fex-emu.gg/Ubuntu_22_04/2025-01-08/Ubuntu_22_04.ero -o ~/Ubuntu_22_04.ero

TIP

You can also use FEXRootFSFetcher, from fex-emu-utils to acquire this image. If you use that tool, it will end up in ~/.local/share/fex-emu/RootFS/Ubuntu_22_04.ero

This image has all the dependencies we need (ty FEX)! We want to use software rendering, as, unsurprisingly, the OAuth flow crashes when trying to use hardware acceleration.

muvm -f ~/Ubuntu_22_04.ero -ti FEXBash "env LIBGL_ALWAYS_SOFTWARE=1 $(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/simulator"

By default, the network inside muvm is isolated from the rest of the system. The easiest workaround for that is to also run monkeydo in muvm (it doesn’t need to be amd64):

$(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/monkeyc \
  -f monkey.jungle \
  -d fr265s_sim \
  -o build/fr265s_sim.prg \
  -y __REPLACE_THIS_WITH_THE_PATH_TO_YOUR__/developer_key \
  -w

muvm $(~/go/bin/connect-iq-sdk-manager-cli sdk current-path)/bin/monkeydo \
  $(pwd)/fr265s_sim.prg \
  fr265s

Note the _sim in the device ID, since we are building for the simulator.

Contact

Follow me on Mastodon (@emilyskidsister@hachyderm.io) or Strava. If there are errors, or you have any comments, please let me know at jocelyn@nettek.ca.

served by Grebedoc made in Neovim