A 258-line C plugin (opentrack.xpl) that runs inside X-Plane and drives
the six pilot-head-pose datarefs from head tracking data written by
opentrack. It is the X-Plane-side counterpart to the "X-Plane" output
protocol in opentrack; the two processes communicate through a POSIX
shared-memory block, not a network socket.
Supported platforms: Linux, macOS (arm64 and x86_64), Windows.
opentrack process X-Plane process
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ "X-Plane" output protocol │ │ opentrack.xpl: │
│ (proto-wine built with │ ── POSIX ──> │ flock()s the shm, reads 6 │
│ -DOTR_WINE_NO_WRAPPER): │ shm_open + │ doubles per frame, drives │
│ writes {x,y,z,yaw,pitch, │ mmap │ sim/graphics/view/ │
│ roll} into shm block │ │ pilots_head_{x,y,z,psi, │
│ /facetracknoir-wine-shm │ │ the,phi} │
└─────────────────────────────┘ └─────────────────────────────┘
The shared-memory block is named facetracknoir-wine-shm (same name used
by the Wine freetrack proxy, so one plugin binary serves both use cases).
The plugin is only compiled when the X-Plane SDK path is provided via the
SDK_XPLANE CMake cache variable; with an empty path it is silently
skipped.
-
Download the X-Plane SDK from developer.x-plane.com/sdk/plugin-sdk-downloads/ (free, ~2 MB zipped). Current version at time of writing:
XPSDK430.zip. -
Unzip it somewhere; note the
SDK/directory inside the zip. -
Configure the opentrack build tree with the SDK path:
cmake -S . -B build -DSDK_XPLANE=/path/to/XPSDK430/SDK cmake --build build --target opentrack-xplane-plugin opentrack-proto-wineopentrack-xplane-plugin→ producesopentrack.xpl(the X-Plane side).opentrack-proto-wine→ producesopentrack-proto-wine.dylib/.so/.dll, the "X-Plane" entry in opentrack's output dropdown.
Both targets need
SDK_XPLANEset. Without it, the output dropdown shows only non-X-Plane protocols.
The output bundle is emitted at x-plane-plugin/opentrack.xpl.
Copy opentrack.xpl into the standard X-Plane plugin layout. The
subdirectory name is platform-specific:
X-Plane 12/
Resources/
plugins/
opentrack/
mac_x64/ ← macOS (arm64 and x86_64)
64/ ← Linux / Windows
opentrack.xpl
One-liner for a typical macOS install:
XP12=~/X-Plane\ 12
mkdir -p "$XP12/Resources/plugins/opentrack/mac_x64"
cp x-plane-plugin/opentrack.xpl "$XP12/Resources/plugins/opentrack/mac_x64/"Verify which path X-Plane actually picked up by grepping its
X-Plane 12/Log.txt for opentrack.xpl after the next launch;
if both mac_x64/ and 64/ trees exist, X-Plane loads one and
silently ignores the other.
On first launch X-Plane's Log.txt (in the sim's root folder) should
contain a line like opentrack init complete emitted by
XPluginStart. If you instead see opentrack failed to init SHM!, the
plugin couldn't open /facetracknoir-wine-shm for RW — check your
filesystem permissions on /dev/shm (Linux) or System Settings →
Privacy sandboxing (macOS).
- In opentrack, pick X-Plane from the Output dropdown. This is
proto-winebuilt in no-wrapper mode; its icon is the X-Plane logo. It writes the shared-memory block the plugin reads. - Start opentrack tracking as usual. The plugin is passive — it just mirrors whatever is currently in the shm block to X-Plane's head-pose datarefs every flight-loop tick.
The plugin registers two X-Plane commands that you can bind to joystick buttons or keys from Settings → Joystick or Settings → Keyboard:
| Command | Description |
|---|---|
opentrack/toggle |
Enable/disable the flight-loop callback. When off, no data is sent to X-Plane's datarefs. |
opentrack/toggle_translation |
Enable/disable the translation part only (yaw/pitch/roll remain active). Re-centers on re-enable. |
Both commands operate on xplm_CommandBegin, so they fire on button
press (not release).
The plugin reads six doubles from the shm and writes to X-Plane:
| shm index | Semantic | Dataref | Conversion |
|---|---|---|---|
| 0 (TX) | x (mm) | sim/graphics/view/pilots_head_x |
mm × 1e-3 + offset_x |
| 1 (TY) | y (mm) | sim/graphics/view/pilots_head_y |
mm × 1e-3 + offset_y |
| 2 (TZ) | z (mm) | sim/graphics/view/pilots_head_z |
mm × 1e-3 + offset_z |
| 3 (Yaw) | yaw (rad) | sim/graphics/view/pilots_head_psi |
rad × 180/π |
| 4 (Pitch) | pitch (rad) | sim/graphics/view/pilots_head_the |
rad × 180/π |
| 5 (Roll) | roll (rad) | sim/graphics/view/pilots_head_phi |
rad × 180/π |
Translation offsets (offset_{x,y,z}) are captured at plugin start and
whenever translation is re-enabled; this lets the user move around their
sim cockpit from a home position that matches the current seat pose.
Defined in both x-plane-plugin/plugin.c and proto-wine/wine-shm.h:
typedef struct WineSHM {
double data[6]; // x,y,z,yaw,pitch,roll (see conversions)
int gameid, gameid2;
unsigned char table[8];
bool stop;
} volatile WineSHM;The X-Plane plugin only reads data[]; the other fields are used by the
Wine freetrack flavor of proto-wine and are ignored here.
Access is guarded with a POSIX flock(fd, LOCK_SH) around the read —
cheap, contention-free for single-writer/single-reader.
The plugin implements the standard X-Plane SDK lifecycle. Each callback
owns specific resources, and teardown happens in reverse order so
Plugin Admin → Reload Plugins is safe (no dangling function pointers
inside the unloaded dylib):
| Callback | Owns | Cleaned up in |
|---|---|---|
XPluginStart |
datarefs, commands, command handlers, SHM mapping | XPluginStop |
XPluginEnable |
flight-loop callback registration | XPluginDisable |
XPluginReceiveMessage |
re-centers offset on AIRPORT_LOADED + PLANE_LOADED | n/a (event-driven) |
XPluginStop unregisters the flight-loop callback and both command
handlers before freeing the SHM, so reload doesn't leave X-Plane
holding pointers into the unloaded dylib.
Failure paths in shm_wrapper_init (malloc / shm_open / ftruncate /
mmap) are checked individually and log the failing syscall with
strerror(errno) to X-Plane's Log.txt for diagnosis. On any failure
XPluginStart returns 0 and X-Plane unloads the plugin cleanly.
- No UDP fallback in the plugin. If you want to drive X-Plane from a different machine, use opentrack's UDP over network output and a different plugin on the X-Plane side. This plugin is local-only.
- Head roll can affect the field-of-view dataref on some X-Plane
builds. The commented-out
field_of_view_roll_degline at the top ofXPluginStartshows the older binding. If you dislike roll acting on FoV on your X-Plane version, swap the two lines and rebuild. - Plugin is not codesigned. On macOS, X-Plane may warn about loading
an unsigned plugin. Either accept the warning or ad-hoc-sign with
codesign --force -s - opentrack.xplbefore installing. volatile WineSHMis mostly cosmetic — it prevents within-thread compiler reordering but doesn't provide cross-process synchronization. The actual safety comes from the surroundingflock(LOCK_SH)/LOCK_EXpair, which the kernel orders with implicit memory barriers.
The minimum API version is set in x-plane-plugin/CMakeLists.txt:
-DXPLM200 -DXPLM210That targets X-Plane 10.00 and newer. If you need features from a newer
SDK (e.g. XPLM400 for X-Plane 12.04+ datarefs), add the corresponding
definition; see the SDK's
version-define documentation
for the full ladder. No new defines are needed for pose tracking as
used here.