本文完整拆解 Windows XInput 手柄协议:API 结构体、按键位掩码、摇杆/扳机取值范围、Guide 键的隐藏调用方式(ordinal 100),以及实测验证的 XInput 状态 → 16 字节 Xbox BLE HID 报文 映射关系。全部代码基于 Python + ctypes,无需第三方库,已在 Windows 11 + Python 3.14 + Flydigi Direwolf 4(Xbox 360 兼容模式,USB VID=0x045E PID=0x028E)上验证通过。
目录
- 协议总览
- XInput API 层
- XINPUT_GAMEPAD 结构体详解
- 按键位掩码表(wButtons)
- 摇杆与扳机的取值规则
- Guide 键的隐藏入口:XInputGetStateEx
- 16 字节 BLE HID 报文协议(实测验证)
- 完整映射实现代码
- 示例输出
- 验证代码
- 完整 GUI 验证程序
- 参考文献与项目
- 附录:AI 调用协议专用 Prompt
1. 协议总览
Windows 上读取 Xbox 手柄有三条路径,各有边界:
| 读取方式 | 可获得的数据 | Guide 键 | 依赖 |
|---|---|---|---|
| XInput API(本文主线) | 16 个按键 + 2 扳机 + 2 摇杆 | ✅(需 ordinal 100) | 系统自带 DLL |
| Raw Input (WM_INPUT) | 原始 HID 报文字节 | 依赖报文定义 | 无 |
| hidapi / HID 直读 | 原始 HID 报文 | 依赖报文定义 | 常被系统驱动独占而失败 |
关键结论:Windows 把蓝牙 HID 手柄的按键集合绑定给键盘/鼠标类系统驱动后,hidapi read() 直接抛 OSError(实测复现),Raw Input 可以绕过但拿到的是被拆分进 HID 集合的字节流。XInput 是唯一稳定、完整、带 Guide 键的方案,所有上层协议转换都应以 XInput 状态为源。
XInput 状态 → 目标报文的两级结构:
物理按键 → XInput (wButtons / b*Trigger / sThumb*) → 16 字节 BLE HID 报文
2. XInput API 层
XInput 是 Windows 自带的 Xbox 手柄 API,运行时位于 XInput1_4.dll(Win8+)等。最多支持 4 个手柄槽位(索引 0~3)。
2.1 加载与查找手柄
返回值为 Win32 错误码:0(ERRORSUCCESS)成功;1167(ERRORDEVICENOTCONNECTED)无手柄。
2.2 轮询模型
XInput 没有事件回调,标准用法是忙轮询(推荐 5~10ms 间隔,约 100~200Hz)。dwPacketNumber 只在状态变化时递增,可用于判断"有没有新数据",但每次都应调用 XInputGetState 保持连接状态检测。
3. XINPUT_GAMEPAD 结构体详解
| 字段 | C 类型 | 范围 | 含义 |
|---|---|---|---|
wButtons | WORD (u16) | 位掩码 | 所有按键/十字键状态 |
bLeftTrigger | BYTE (u8) | 0~255 | 左扳机模拟量,0=松开 |
bRightTrigger | BYTE (u8) | 0~255 | 右扳机模拟量 |
sThumbLX | SHORT (i16) | -32768~32767 | 左摇杆 X,0=居中 |
sThumbLY | SHORT (i16) | -32768~32767 | 左摇杆 Y,向上为正 |
sThumbRX / sThumbRY | SHORT (i16) | -32768~32767 | 右摇杆 |
注意:sThumb* 理论中心值为 0,但物理摇杆静止时常见 ±256 以内的偏置(实测 LY 静止值 = -256),这是 XInput 固件层量化误差,应用层需用死区过滤。
4. 按键位掩码表(wButtons)
这是协议的核心常量表,与 xinput.h 逐位一致:
易错点(本项目实测踩过):XInput 位掩码与 16 字节 HID 协议的位掩码完全不同。例如 XInput 里 A=0x1000,HID 协议里 A=0x01。两套编码绝不能混用。
三个死区/阈值常量(xinput.h)
5. 摇杆与扳机的取值规则
- 摇杆:i16 原始值,X 右为正、Y 上为正;死区内应视为 0
- 扳机:u8 原始值 0~255;某些手柄在未按下时会给 1~5 的底噪,判定"按下"用阈值 30
- 死区处理的标准做法(径向死区,来自 XInput 官方文档):
6. Guide 键的隐藏入口:XInputGetStateEx
XInputGetState 的公开文档不包含 Guide(Xbox/Home)键——wButtons 位 0x0400 永远读不到。真实存在一个未公开导出:XInputGetStateEx,位于 XInput1_4.dll 的 ordinal 100,结构与 XInputGetState 完全一致,但能读到 Guide 键。
实测:本环境 XInput1_4.dll ordinal 100 可用,按住 Home 键时 wButtons 中 0x0400 置位。
7. 16 字节 BLE HID 报文协议(实测验证)
Xbox 手柄在 BLE 模式下通过 HID over GATT(Report UUID 0x2A4D)上报 16 字节输入报告。Windows 将其解析为 XInput 状态;反向映射(XInput → 报文)即 ESP32 等设备直连 GATT 时收到的原始字节。全部 16 字节均为小端序。
7.1 完整字节布局
| 偏移 | 长度 | 字段 | 编码 | 中值/默认 |
|---|---|---|---|---|
| 0–1 | u16 LE | joyLHori 左摇杆 X | u16,中值 0x8000 | 0x8000 |
| 2–3 | u16 LE | joyLVert 左摇杆 Y | u16,中值 0x8000 | 0x8000 |
| 4–5 | u16 LE | joyRHori 右摇杆 X | u16 | 0x8000 |
| 6–7 | u16 LE | joyRVert 右摇杆 Y | u16 | 0x8000 |
| 8–9 | u16 LE | trigLT 左扳机 | 10 位,0~1023,0=松开 | 0 |
| 10–11 | u16 LE | trigRT 右扳机 | 10 位 | 0 |
| 12 | u8 | 十字键帽子值 | 0中/1上/2右上/3右/4右下/5下/6左下/7左/8左上 | 0 |
| 13 | u8 | 动作键位掩码 | A=0x01 B=0x02 X=0x08 Y=0x10 LB=0x40 RB=0x80 | 0 |
| 14 | u8 | 系统键位掩码 | View=0x04 Menu=0x08 Xbox=0x10 LS=0x20 RS=0x40 | 0 |
| 15 | u8 | 保留 | Share=0x01(原装 Xbox Series 手柄) | 0 |
7.2 帽子值(hat)编码
十字键不是位掩码而是 8 方向 + 中值的枚举(顺时针递增):
1
8 2
7 3
6 4
5
同时按下两个方向(如上+右)时帽子值取中间值(上+右=2)。
7.3 XInput → 16 字节映射规则
| XInput 值 | 协议值 | 转换 |
|---|---|---|
sThumb* (i16) | u16 LE | value & 0xFFFF(即 +0x8000 环绕:0 → 0x8000) |
bLeftTrigger (0~255) | 10-bit | min(1023, value * 4) |
| DPAD 组合 | hat | 1上 3右 5下 7左,斜向 2/4/6/8 |
| A/B/X/Y/LB/RB | byte[13] | A|B<<1|X<<3|Y<<4|LB<<6|RB<<7 |
| Back/Start/Guide/L3/R3 | byte[14] | View<<2 |Menu<<3|Xbox<<4|L3<<5|R3<<6 |
| Share | byte[15] | XInput 不暴露,恒 0 |
实测差异提醒:手柄静止时 sThumbLY 常为 -256(固件量化偏置),映射后为 0xFF 0xFF 而非 0x00 0x80,属正常现象,上层需死区归零。
8. 完整映射实现代码
以下代码为本项目实测通过的完整转换函数:
9. 示例输出
9.1 静止状态(实测)
slot: 0
state: 0 wButtons=0000 LT=0 RT=0 LX=0 LY=-256
proto: 00 00 00 FF 00 00 00 FF 00 00 00 00 00 00 00 00
注意 LY 静止偏置 -256 → 报文 [2:4] = FF FF(即 0xFFFF,距中值 0x8000 偏 -1 刻度),上层需死区处理。
9.2 按住 A 键 + 左摇杆推右
9.3 按住十字键右上
wButtons=0x0009 (D-Pad Up + D-Pad Right)
proto: ... 00 00 00 00 02 00 00 00
└ hat=2 (右上)
9.4 按住 Home(Guide) 键(仅 Ex 版)
wButtons=0x0400 (Guide)
proto: ... 00 00 00 00 00 10 00 00
└ byte[14]=0x10 (Xbox)
10. 验证代码
逐键验证脚本:提示一次按一个键,比对 wButtons 与 16 字节报文的对应位是否一致。
预期输出(按 B 键时):
Controller slot 0, Guide support: True
OK B wButtons=0x2000 -> proto[13]&0x02 = 2
11. 完整 GUI 验证程序

image-20260919131422647xinput_gui.py 功能一览:
- 按键灯:12 键实时显示(用 XInput 位判断,不是协议位)
- 摇杆:两个 170×170 画布,红点实时映射(Y 轴屏幕取反)
- 扳机:LT/RT 进度条 0~255
- 十字键:显示帽子值与方向名
- 协议视图:实时 16 字节 hex + 各字段分解值
- 状态栏:手柄槽位、帧率、XInput 包号、Guide 键支持情况
核心设计要点:
- tkinter
after(8)非阻塞轮询(~100Hz),不用线程即可不卡 UI XInputGetStateEx(ordinal 100)启动时探测,决定 Guide 键是否可用- 按键灯与协议视图双编码并存:灯用 XInput 位(
LAMPS13/LAMPS14),协议视图用 HID 位(xinput_to_protocol()),两套掩码绝不混用
完整源码(实测通过版):
12. 参考文献与项目
官方文档
- XInput 概述 — Microsoft Learn https://learn.microsoft.com/en-us/windows/win32/xinput/introduction-to-xinput
- XInputGetState — Microsoft Learn https://learn.microsoft.com/en-us/windows/win32/xinput/xinputgetstate
- XINPUT_GAMEPAD structure — Microsoft Learn(含死区常量定义) https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad
- XInput Controllers, DirectInput, and XUSB Devices(Guide 键/映射表权威说明) https://learn.microsoft.com/en-us/windows/win32/xinput/xinput-and-directinput
- HID Usage Tables(十字键帽子值 Hat Switch 定义,Generic Desktop Usage 0x39) https://usb.org/sites/default/files/hut1_41.pdf
关键非公开知识
- XInputGetStateEx = ordinal 100:
XInput1_4.dll未公开导出,结构与XInputGetState一致,多读出 Guide 键(0x0400)。社区佐证:x360ce 源码xinput1_4/Forwarder.cpp、 pygameXInputPython移植笔记。 - 16 字节 BLE HID 报文布局:Xbox Wireless Controller BLE HID Input Report(0x2A4D,Report ID 0x01),社区逆向项目对照:
xpadLinux 内核驱动 (drivers/input/joystick/xpad.c) — USB/BLE 报文处理- ESP32 NimBLE Xbox controller 库(如
StackSizes/XboxControllerNotificationParser)中onNotify的字节偏移定义与本文第 7 节一致
本项目产出物
| 文件 | 说明 |
|---|---|
read_xinput.py | 命令行原始读数(变化打印) |
xinput_gui.py | GUI 协议验证程序(本文第 11 节) |
ble_protocol_gui.py | Raw Input 版验证(备用方案,hidapi 被系统独占时不可用) |
13. 附录:AI 调用协议专用 Prompt
将以下内容直接作为系统提示词/上下文交给 AI,即可让其正确生成操作该手柄协议的代码。
协议验证日期:2026-09-19;环境:Windows 11 26200 / Python 3.14.6 / Flydigi Direwolf 4 (Xbox 兼容模式)。