|
| 1 | +import ctypes, ctypes.util |
| 2 | + |
| 3 | +from ctypes import Structure, POINTER |
| 4 | + |
| 5 | +from math import sqrt |
| 6 | + |
| 7 | +import glm |
| 8 | + |
| 9 | +XINPUT_DLL_NAMES = ( |
| 10 | + "XInput1_4.dll", |
| 11 | + "XInput9_1_0.dll", |
| 12 | + "XInput1_3.dll", |
| 13 | + "XInput1_2.dll", |
| 14 | + "XInput1_1.dll" |
| 15 | +) |
| 16 | + |
| 17 | +libXInput = None |
| 18 | + |
| 19 | +for name in XINPUT_DLL_NAMES: |
| 20 | + found = ctypes.util.find_library(name) |
| 21 | + if found: |
| 22 | + libXInput = ctypes.CDLL(found) |
| 23 | + break |
| 24 | + |
| 25 | +if not libXInput: |
| 26 | + raise IOError("XInput library was not found.") |
| 27 | + |
| 28 | +WORD = ctypes.c_ushort |
| 29 | +BYTE = ctypes.c_ubyte |
| 30 | +SHORT = ctypes.c_short |
| 31 | +DWORD = ctypes.c_ulong |
| 32 | + |
| 33 | +ERROR_SUCCESS = 0 |
| 34 | +ERROR_DEVICE_NOT_CONNECTED = 1167 |
| 35 | + |
| 36 | +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849 |
| 37 | +XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689 |
| 38 | +XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30 |
| 39 | + |
| 40 | +BATTERY_DEVTYPE_GAMEPAD = 0x00 |
| 41 | +BATTERY_TYPE_DISCONNECTED = 0x00 |
| 42 | +BATTERY_TYPE_WIRED = 0x01 |
| 43 | +BATTERY_TYPE_ALKALINE = 0x02 |
| 44 | +BATTERY_TYPE_NIMH = 0x03 |
| 45 | +BATTERY_TYPE_UNKNOWN = 0xFF |
| 46 | +BATTERY_LEVEL_EMPTY = 0x00 |
| 47 | +BATTERY_LEVEL_LOW = 0x01 |
| 48 | +BATTERY_LEVEL_MEDIUM = 0x02 |
| 49 | +BATTERY_LEVEL_FULL = 0x03 |
| 50 | + |
| 51 | +_battery_type_dict = {BATTERY_TYPE_DISCONNECTED : "DISCONNECTED", |
| 52 | + BATTERY_TYPE_WIRED : "WIRED", |
| 53 | + BATTERY_TYPE_ALKALINE : "ALKALINE", |
| 54 | + BATTERY_TYPE_NIMH : "NIMH", |
| 55 | + BATTERY_TYPE_UNKNOWN : "UNKNOWN"} |
| 56 | + |
| 57 | +_battery_level_dict = {BATTERY_LEVEL_EMPTY : "EMPTY", |
| 58 | + BATTERY_LEVEL_LOW : "LOW", |
| 59 | + BATTERY_LEVEL_MEDIUM : "MEDIUM", |
| 60 | + BATTERY_LEVEL_FULL : "FULL"} |
| 61 | + |
| 62 | +class XINPUT_GAMEPAD(Structure): |
| 63 | + _fields_ = [("wButtons", WORD), |
| 64 | + ("bLeftTrigger", BYTE), |
| 65 | + ("bRightTrigger", BYTE), |
| 66 | + ("sThumbLX", SHORT), |
| 67 | + ("sThumbLY", SHORT), |
| 68 | + ("sThumbRX", SHORT), |
| 69 | + ("sThumbRY", SHORT), |
| 70 | + ] |
| 71 | + |
| 72 | +class XINPUT_STATE(Structure): |
| 73 | + _fields_ = [("dwPacketNumber", DWORD), |
| 74 | + ("Gamepad", XINPUT_GAMEPAD), |
| 75 | + ] |
| 76 | + |
| 77 | +State = XINPUT_STATE |
| 78 | + |
| 79 | +class XINPUT_VIBRATION(Structure): |
| 80 | + _fields_ = [("wLeftMotorSpeed", WORD), |
| 81 | + ("wRightMotorSpeed", WORD), |
| 82 | + ] |
| 83 | + |
| 84 | +class XINPUT_BATTERY_INFORMATION(Structure): |
| 85 | + _fields_ = [("BatteryType", BYTE), |
| 86 | + ("BatteryLevel", BYTE), |
| 87 | + ] |
| 88 | + |
| 89 | +libXInput.XInputGetState.argtypes = [DWORD, POINTER(XINPUT_STATE)] |
| 90 | +libXInput.XInputGetState.restype = DWORD |
| 91 | + |
| 92 | +def XInputGetState(dwUserIndex, state): |
| 93 | + return libXInput.XInputGetState(dwUserIndex, ctypes.byref(state)) |
| 94 | + |
| 95 | +libXInput.XInputSetState.argtypes = [DWORD, POINTER(XINPUT_VIBRATION)] |
| 96 | +libXInput.XInputSetState.restype = DWORD |
| 97 | + |
| 98 | +def XInputSetState(dwUserIndex, vibration): |
| 99 | + return libXInput.XInputSetState(dwUserIndex, ctypes.byref(vibration)) |
| 100 | + |
| 101 | +libXInput.XInputGetBatteryInformation.argtypes = [DWORD, BYTE, POINTER(XINPUT_BATTERY_INFORMATION)] |
| 102 | +libXInput.XInputGetBatteryInformation.restype = DWORD |
| 103 | + |
| 104 | +def XInputGetBatteryInformation(dwUserIndex, devType, batteryInformation): |
| 105 | + return libXInput.XInputGetBatteryInformation(dwUserIndex, devType, ctypes.byref(batteryInformation)) |
| 106 | + |
| 107 | + |
| 108 | +class XInputNotConnectedError(Exception): |
| 109 | + pass |
| 110 | + |
| 111 | +def get_state(user_index): |
| 112 | + state = XINPUT_STATE() |
| 113 | + res = XInputGetState(user_index, state) |
| 114 | + if res == ERROR_DEVICE_NOT_CONNECTED: |
| 115 | + raise XInputNotConnectedError("Controller [{}] appears to be disconnected.".format(user_index)) |
| 116 | + |
| 117 | + assert res == 0, "Couldn't get the state of controller [{}]. Is it disconnected?".format(user_index) |
| 118 | + |
| 119 | + return state |
| 120 | + |
| 121 | +def get_battery_information(user_index): |
| 122 | + battery_information = XINPUT_BATTERY_INFORMATION() |
| 123 | + XInputGetBatteryInformation(user_index, BATTERY_DEVTYPE_GAMEPAD, battery_information) |
| 124 | + return (_battery_type_dict[battery_information.BatteryType], _battery_level_dict[battery_information.BatteryLevel]) |
| 125 | + |
| 126 | +def set_vibration(user_index, left_speed, right_speed): |
| 127 | + if type(left_speed) == float and left_speed <= 1.0: |
| 128 | + left_speed = (round(65535 * left_speed, 0)) |
| 129 | + |
| 130 | + if type(right_speed) == float and right_speed <= 1.0: |
| 131 | + right_speed = (round(65535 * right_speed, 0)) |
| 132 | + |
| 133 | + vibration = XINPUT_VIBRATION() |
| 134 | + |
| 135 | + vibration.wLeftMotorSpeed = int(left_speed) |
| 136 | + vibration.wRightMotorSpeed = int(right_speed) |
| 137 | + |
| 138 | + return XInputSetState(user_index, vibration) |
| 139 | + |
| 140 | +def get_button_values(state): |
| 141 | + wButtons = state.Gamepad.wButtons |
| 142 | + return {"DPAD_UP" : bool(wButtons & 0x0001), |
| 143 | + "DPAD_DOWN" : bool(wButtons & 0x0002), |
| 144 | + "DPAD_LEFT" : bool(wButtons & 0x0004), |
| 145 | + "DPAD_RIGHT" : bool(wButtons & 0x0008), |
| 146 | + "START" : bool(wButtons & 0x0010), |
| 147 | + "BACK" : bool(wButtons & 0x0020), |
| 148 | + "LEFT_THUMB" : bool(wButtons & 0x0040), |
| 149 | + "RIGHT_THUMB" : bool(wButtons & 0x0080), |
| 150 | + "LEFT_SHOULDER" : bool(wButtons & 0x0100), |
| 151 | + "RIGHT_SHOULDER" : bool(wButtons & 0x0200), |
| 152 | + "A" : bool(wButtons & 0x1000), |
| 153 | + "B" : bool(wButtons & 0x2000), |
| 154 | + "X" : bool(wButtons & 0x4000), |
| 155 | + "Y" : bool(wButtons & 0x8000), |
| 156 | + } |
| 157 | + |
| 158 | +def get_trigger_values(state): |
| 159 | + LT = state.Gamepad.bLeftTrigger |
| 160 | + RT = state.Gamepad.bRightTrigger |
| 161 | + |
| 162 | + normLT = 0 |
| 163 | + normRT = 0 |
| 164 | + |
| 165 | + if LT > XINPUT_GAMEPAD_TRIGGER_THRESHOLD: |
| 166 | + LT -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD |
| 167 | + normLT = LT / (255 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) |
| 168 | + else: |
| 169 | + LT = 0 |
| 170 | + |
| 171 | + if RT > XINPUT_GAMEPAD_TRIGGER_THRESHOLD: |
| 172 | + RT -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD |
| 173 | + normRT = RT / (255 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) |
| 174 | + else: |
| 175 | + RT = 0 |
| 176 | + |
| 177 | + return (normLT, normRT) |
| 178 | + |
| 179 | +def get_thumb_values(state): |
| 180 | + LX = state.Gamepad.sThumbLX |
| 181 | + LY = state.Gamepad.sThumbLY |
| 182 | + RX = state.Gamepad.sThumbRX |
| 183 | + RY = state.Gamepad.sThumbRY |
| 184 | + |
| 185 | + magL = sqrt(LX*LX + LY*LY) |
| 186 | + magR = sqrt(RX*RX + RY*RY) |
| 187 | + |
| 188 | + normLX = LX / magL |
| 189 | + normLY = LY / magL |
| 190 | + normRX = RX / magR |
| 191 | + normRY = RY / magR |
| 192 | + |
| 193 | + normMagL = 0 |
| 194 | + normMagR = 0 |
| 195 | + |
| 196 | + if (magL > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE): |
| 197 | + magL = min(32767, magL) |
| 198 | + |
| 199 | + magL -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE |
| 200 | + |
| 201 | + normMagL = magL / (32767 - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) |
| 202 | + else: |
| 203 | + magL = 0 |
| 204 | + |
| 205 | + if (magR > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE): |
| 206 | + magR = min(32767, magR) |
| 207 | + |
| 208 | + magR -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE |
| 209 | + |
| 210 | + normMagR = magR / (32767 - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) |
| 211 | + else: |
| 212 | + magR = 0 |
| 213 | + |
| 214 | + return (glm.vec2(normLX, normLY) * normMagL, glm.vec2(normRX, normRY) * normMagR) |
| 215 | + |
0 commit comments