Swift Package for BrlAPI with some helpers.
swift package build-brltty
swift build
swift testbuild-brltty is a command plugin that initialises git submodules and runs Scripts/build-brltty-macos.sh. Pass -- --no-clean to skip the configure step on subsequent builds:
swift package build-brltty -- --no-cleanPass -- --arch=arm64 or -- --arch=x86_64 to build for a specific architecture:
swift package build-brltty -- --arch=arm64To build the xcframework directly from the plugin (equivalent to running the script manually):
swift package build-brltty -- --xcframework --universal
swift package build-brltty -- --xcframework --universal --no-cleanRun this on Apple Silicon — Rosetta 2 (installed by default) lets configure test binaries for x86_64 execute during the cross-compile:
./Scripts/create-brlapi-xcframework.sh --universalOr via the plugin:
swift package build-brltty -- --xcframework --universalThis compiles BRLTTY for arm64 and x86_64, lipos the dylibs into a fat binary, and produces:
BrlAPI.xcframework— the binary target consumed by SPMBrlAPI.xcframework.zip— the release asset- The SPM checksum printed to stdout — note it
Pass --no-clean to reuse existing BRLTTY build outputs:
./Scripts/create-brlapi-xcframework.sh --universal --no-clean# Confirm both architectures are present
lipo -info BrlAPI.xcframework/macos-arm64_x86_64/BrlAPI.framework/BrlAPI(If the file isn't there, you may have forgotten --universal, which will change the path)
# Build and test against the local binary target
# (Package.swift detects BrlAPI.xcframework and switches automatically)
swift build
swift test
The framework binaries must be signed with a Developer ID certificate before release.
codesign --force --sign "Developer ID Application: Your Name (TEAMID)" \
--timestamp \
BrlAPI.xcframework/macos-arm64_x86_64/BrlAPI.framework/Versions/A/BrlAPI
codesign --force --sign "Developer ID Application: Your Name (TEAMID)" \
--timestamp \
BrlAPI.xcframework/macos-arm64_x86_64/BrlAPI.framework
codesign --force --sign "Developer ID Application: Your Name (TEAMID)" \
--timestamp \
BrlAPI.xcframeworkVerify the binary has a valid Developer ID signature:
codesign -dv BrlAPI.xcframework/macos-arm64_x86_64/BrlAPI.framework/Versions/A/BrlAPI
# TeamIdentifier should match your team ID; no "adhoc" in the flagsThen notarize:
ditto -c -k --keepParent BrlAPI.xcframework BrlAPI.xcframework.zip
xcrun notarytool submit BrlAPI.xcframework.zip \
--apple-id "your@email.com" \
--team-id "TEAMID" \
--waitThe checksum will change after signing — recompute it:
swift package compute-checksum BrlAPI.xcframework.zipReplace the conditional BrlAPI target with a URL binary target. You can construct the URL before uploading since GitHub release asset URLs are deterministic:
.binaryTarget(
name: "BrlAPI",
url: "https://github.com/rustle/Braille/releases/download/1.0.0/BrlAPI.xcframework.zip",
checksum: "<checksum from step 1>"
),Commit this change.
git tag 1.0.0
git push origin main --tags
gh release create 1.0.0 BrlAPI.xcframework.zip --title "1.0.0"Package.swift at a release tag uses the URL binary target, so there is no local build path in the shipped manifest. Consumers who want to work on the Braille package itself reference it via a local override in their consuming project:
// In SpeakUp or ScreenReader Package.swift
.package(path: "../Braille")This bypasses the binary target entirely and uses the source package directly.
Run the daemon without -q and with --log-level=debug to see each BrlAPI write it receives:
sudo .build/brltty/Programs/brltty -b no -x no -n -A auth=none --log-level=debugapitest is a BrlAPI client that connects to the running daemon and sends test patterns — the same path your code uses. With the daemon running:
.build/brltty/Programs/apitestbrltest exercises braille drivers directly (bypasses BrlAPI). Useful for confirming a driver works independently of the daemon:
.build/brltty/Programs/brltest -b noBoth apitest and brltest are built by ./Scripts/build-brltty-macos.sh.
BrlAPIDisplay connects to a running BRLTTY daemon over a Unix socket. The daemon must be running before connect() is called; if it isn't, the call throws BrlAPIError.connectionFailed.
Run BRLTTY with the null drivers so the socket is available without hardware:
sudo .build/brltty/Programs/brltty -b no -x no -n -q -A auth=none-b no— null Braille driver (simulates a display)-x no— null screen driver-n— don't daemonize (stays in foreground)-q— quiet-A auth=none— disable key-file authentication (the app runs as a non-root user and cannot read root's auth key)
The simulated display accepts write calls silently, so the full send path can be exercised.
sudo .build/brltty/Programs/brlttyBRLTTY auto-detects most USB and Bluetooth displays. Pass -b <driver> to force a specific driver if needed.
- BRLTTY needs
root(or USB/Accessibility permissions) to open the display device. - The socket is created at
/var/run/BrlAPI.socket(root) or~/.BrlAPI.socket(user). - If
/etc/brltty.confis absent, pass all options on the command line as shown above.
liblouis is vendored into this package as a Swift Package Manager C target (Sources/CLiblouis/) and compiled from source as part of a normal swift build — no separate build step or system install required. The liblouis source is a git submodule at liblouis/; run git submodule update --init to populate it.
The translation tables live in liblouis/tables/. At compile time TABLESDIR is set to an empty string so liblouis makes no assumptions about the on-disk location; the tables are resolved at runtime via Bundle.module (see below).
To update the vendored sources to a newer liblouis release, advance the submodule and run Scripts/build-liblouis-macos.sh to regenerate the derived headers (config.h, liblouis.h) that are checked in alongside the C sources.
BrailleTranslator needs the liblouis translation tables at runtime. The Braille package declares liblouis/tables as a resource, so they are bundled automatically and resolved via Bundle.module — no extra wiring is required when using this package via SPM.
For non-SPM embedding (e.g. adding the Braille sources directly to an Xcode project), pass an explicit path:
BrailleTranslator(tablesDirectory: Bundle.main.url(forResource: "tables", withExtension: nil)?.path)Or set the LOUIS_TABLEPATH environment variable to the absolute path of the tables directory before the process starts.
Braille is released under an Apache license. See the LICENSE file for more information.