Skip to content

Push-to-talk from hardware

Available now. A physical yoke or joystick button works as push-to-talk, alongside the radio panel's on-screen button and the browser's held-Space-key binding -- all three trigger the exact same key-up, so the TX light and transcript behave identically no matter which one you use. Configure this from the Settings page's Voice tab -- see Settings reference for the full field list.

voice.ptt_source picks how: X-Plane, Joystick, or Off. The default is X-Plane on Windows and Off elsewhere. If you set up a joystick PTT button before this setting existed, xatc keeps using it (the Joystick source), and --ptt-joystick on the command line also means the Joystick source.

X-Plane

Reads sim/joystick/joystick_button_values -- X-Plane's own array of every joystick button's current state -- straight over the Web API connection xatc already has open for AircraftState. Confirmed live against a real yoke on Windows (X-Plane 12.4.3): the PTT button showed up at index 1441. No extra library and no SDL are involved, so unlike the Joystick source below, this works on macOS too.

Click Learn PTT button on the Voice tab, then press the button on your yoke or joystick: xatc watches the array for up to 10 seconds and fills in the index that changed. This needs a live X-Plane connection -- there's nothing to read from during a replay session, and the button click reports a clear error rather than hanging if you try it during one.

Switching to or away from this source, or changing the button index, applies immediately -- no restart needed.

Joystick

The original approach, kept as a fallback for a device X-Plane doesn't expose a usable button index for: xatc.voice.ptt_input polls a chosen button via SDL (the pygame-ce joystick module) in a background thread.

The Voice tab's Joystick PTT field takes "<device name or index>:<button>" -- either the device's index ("0:7") or a case-insensitive substring of its name ("CH Yoke:7"). Learn PTT button works here too, watching every connected device's every button instead of one array. A device name that doesn't match anything connected, or matches more than one device ambiguously, is a clean error naming what's actually plugged in, not a crash. If you'd rather find a button number yourself first:

xatc ptt-probe

lists every connected joystick and yoke, then prints each button's number as you press and release it.

Why macOS refuses this source

SDL (the library behind Joystick PTT) expects its event pumping to happen on the main thread. Polling in a background thread -- needed so the rest of xatc keeps running normally while it waits for a button press -- is fine on Windows, which is where xatc actually runs for a real flight (see Any-airport data loading and ADR 0006 in the repository), but can crash the process on macOS. Rather than risk that, this source checks the platform and refuses to start there, with a message explaining why -- pick X-Plane or Off instead. xatc ptt-probe is unaffected, since it polls from the main thread and never starts that background thread at all -- it works fine on macOS for finding a button number ahead of time.

Why pygame-ce

Chose SDL (via pygame-ce's joystick module) over a raw hid binding or the inputs package: SDL enumerates an arbitrary joystick or yoke's full button set the same way for any device it recognizes, which a generic "any device, any button" binding needs -- inputs has much narrower generic-joystick coverage (mostly Xbox-style controllers), and hid would need this module to know each device's raw HID report layout itself. Specifically pygame-ce (an actively maintained, API-compatible fork of the original pygame, still import pygame), not plain pygame: plain pygame's newest release predates Python 3.14 and ships no wheel for it, which would mean building it from source in CI -- pygame-ce publishes one for every platform this project runs on.

Off

No hardware push-to-talk at all -- the on-screen button and the Space-key binding still work.

Limitations

  • Joystick specifically hasn't been confirmed against real hardware -- its polling logic, spec parsing, and device resolution are all thoroughly unit-tested against a fake backend, and the real backend constructs and enumerates cleanly on a machine with no joystick attached, but the actual "press a real button, hear it key up" path hasn't been exercised against real Joystick-source hardware. (X-Plane, above, has.)
  • Joystick is Windows only, by design -- see above.

Verification

Unit tests cover spec parsing (including a device name that itself contains a colon), device resolution (by index, by name substring, an ambiguous match, no match, no devices connected at all), the press/release polling loop for both sources (fires once per press, once per release, never repeatedly while held, never fires if the button's never pressed), xatc ptt-probe's device listing and press/release output, and XPlaneWebApiBridge.dataref_stream against a real local fake X-Plane Web API server -- all without needing pygame installed, real hardware, or a real X-Plane connection. Separately, tests confirm a trigger from either source reaches the exact same panel code the WebSocket-driven on-screen button and keyboard binding use, that the Joystick source's pygame resources are released in every case (a resolve failure, the macOS refusal, and a normal shutdown), and that hot-swapping ptt_source mid-session starts and stops the right one.