1. Problem Description

In a Linux environment, when using an Xbox Series X/S or Xbox One controller to operate X-Plane 12, you may encounter a classic "rudder deadlock" issue:

  • Symptom: The default xpad driver recognizes LT and RT as two independent absolute axes ranging from 0 to 100%.
  • Conflict: X-Plane's Yaw axis expects a single centered axis (ranging from -1 to +1). If you map LT to Rudder Left and RT to Rudder Right, it may work acceptably on smaller aircraft like the Cessna, but on advanced plugin aircraft such as the ZIBO 737, the rudder typically gets stuck on one side and cannot be centered or operated at all.

2. Why not use xboxdrv?

The traditional solution is to use xboxdrv --trigger-as-zaxis. However, for modern controllers (especially those connected via Bluetooth or newer Series X models):

  1. xboxdrv has not been updated in 15 years and fails to recognize USB Endpoints on newer controllers, frequently throwing the error couldn't find matching endpoint.
  2. xboxdrv starts unstably and often causes controller permission conflicts.

3. AnduinOS Best Practices: Python Axis Bridging Logic

We manually intercept LT/RT signals using Python's evdev library and create a virtual gamepad device at the lower level that never disconnects.

Core Logic:

  • Calculation formula: Merged_Axis = (RT_Value - LT_Value)
  • Range mapping: Maps the original 0–1023 range to the standard 16-bit joystick range (-32768 to 32767), providing higher control surface precision.
  • Persistence: The virtual device is created only once when the service starts, so X-Plane does not lose calibration information even when a physical controller is plugged in or unplugged.

4. Implementation Plan

A. Write the bridge script ~/xbox_rudder_bridge.py

#!/usr/bin/env python3
import evdev
from evdev import ecodes, UInput, AbsInfo
import time

def find_controller():
    try:
        devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
        for device in devices:
            name = device.name
            # 匹配主控制器,排除鼠标/键盘模拟节点
            if "Xbox" in name and not any(x in name for x in ["Mouse", "Keyboard", "Consumer", "Virtual"]):
                return device
    except Exception:
        pass
    return None

def main():
    # 创建高精度持久化虚拟设备
    cap = {
        ecodes.EV_ABS: [
            (ecodes.ABS_RZ, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0))
        ],
        ecodes.EV_KEY: [ecodes.BTN_JOYSTICK]
    }
    vdev = UInput(cap, name="Anduin Virtual Rudder", vendor=0x1234, product=0x5678)
    
    while True:
        device = find_controller()
        if not device:
            time.sleep(5)
            continue
            
        try:
            lt_val, rt_val = 0, 0
            for event in device.read_loop():
                if event.type == ecodes.EV_ABS:
                    # 适配不同连接模式下的触发器代码
                    if event.code in [ecodes.ABS_Z, ecodes.ABS_BRAKE]:
                        lt_val = event.value
                    elif event.code in [ecodes.ABS_RZ, ecodes.ABS_GAS]:
                        rt_val = event.value
                    
                    # 映射并输出
                    merged_val = (rt_val - lt_val) * 32
                    vdev.write(ecodes.EV_ABS, ecodes.ABS_RZ, merged_val)
                    vdev.syn()
        except (OSError, FileNotFoundError):
            time.sleep(2)

B. Configure the Systemd Service

Create /etc/systemd/system/xbox-rudder.service:

[Unit]
Description=Python Xbox Rudder Trigger Bridge
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 -u /home/anduin/xbox_rudder_bridge.py
Restart=always
User=root

[Install]
WantedBy=multi-user.target

C. Resolving Permission and Identification Issues

Create the udev rule /etc/udev/rules.d/99-xbox-rudder.rules:

SUBSYSTEM=="input", ATTRS{name}=="Anduin Virtual Rudder", ENV{ID_INPUT}="1", ENV{ID_INPUT_JOYSTICK}="1", MODE="0666"

5. X-Plane 12 Setup Guide

  1. Restart the service: sudo systemctl restart xbox-rudder.service.
  2. Go to X-Plane settings, where you will see a new device called Anduin Virtual Rudder.
  3. Bind its only axis to Yaw.
  4. In the original Xbox controller settings, remove all bindings related to Rudder or Yaw.
  5. Calibrate once, and no further calibration is needed even after restarting the computer.

By Anduin, 2026. Wishing all Linux aviation enthusiasts safe takeoffs and landings.