I have a USB controller that I'm trying to get inputs from, a Microsoft® SideWinder® Plug & Play Game Pad. I'm having difficulties trying to figure out how to receive its inputs correctly. Unfortunately, I cannot use pygame as it requires a window to receive inputs from, but I have to generate a pyglet window (via PsychoPy) to run my program. With pygame it can connect and show the state of buttons, but it cannot receive inputs without creating a window. I tried looking for other libraries, but all I encountered was Inputs, which isn't compatible with my controller (doesn't detect the device after installing). The controller itself works as I've tested it with an online gamepad tester. PsychoPy's joystick API is currently broken and does not work, so no luck there either.
I was really hoping anyone had advice on how to receive inputs from my controller/gamepad into my program?
For Windows you can use the WINMM.dll directly.
Use the ctypes library to load the dll (see Loading shared libraries). Use ctypes.WINFUNCTYPE
to create prototypes for the function:
import ctypes
winmmdll = ctypes.WinDLL('winmm.dll')
# [joyGetNumDevs](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetnumdevs)
"""
UINT joyGetNumDevs();
"""
joyGetNumDevs_proto = ctypes.WINFUNCTYPE(ctypes.c_uint)
joyGetNumDevs_func = joyGetNumDevs_proto(("joyGetNumDevs", winmmdll))
# [joyGetDevCaps](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetdevcaps)
"""
MMRESULT joyGetDevCaps(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc);
32 bit: joyGetDevCapsA
64 bit: joyGetDevCapsW
sizeof(JOYCAPS): 728
"""
joyGetDevCaps_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint)
joyGetDevCaps_param = (1, "uJoyID", 0), (1, "pjc", None), (1, "cbjc", 0)
joyGetDevCaps_func = joyGetDevCaps_proto(("joyGetDevCapsW", winmmdll), joyGetDevCaps_param)
# [joyGetPosEx](https://learn.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetposex)
"""
MMRESULT joyGetPosEx(UINT uJoyID, LPJOYINFOEX pji);
sizeof(JOYINFOEX): 52
"""
joyGetPosEx_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p)
joyGetPosEx_param = (1, "uJoyID", 0), (1, "pji", None)
joyGetPosEx_func = joyGetPosEx_proto(("joyGetPosEx", winmmdll), joyGetPosEx_param)
Create the python function joyGetNumDevs
, joyGetDevCaps
and joyGetPosEx
which delegate to the DLL. And create classes for the types JOYCAPS
respectively JOYINFOEX
:
# joystickapi - joyGetNumDevs
def joyGetNumDevs():
try:
num = joyGetNumDevs_func()
except:
num = 0
return num
# joystickapi - joyGetDevCaps
def joyGetDevCaps(uJoyID):
try:
buffer = (ctypes.c_ubyte * JOYCAPS.SIZE_W)()
p1 = ctypes.c_uint(uJoyID)
p2 = ctypes.cast(buffer, ctypes.c_void_p)
p3 = ctypes.c_uint(JOYCAPS.SIZE_W)
ret_val = joyGetDevCaps_func(p1, p2, p3)
ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYCAPS(buffer))
except:
ret = False, None
return ret
# joystickapi - joyGetPosEx
def joyGetPosEx(uJoyID):
try:
buffer = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4))()
buffer[0] = JOYINFOEX.SIZE
buffer[1] = JOY_RETURNALL
p1 = ctypes.c_uint(uJoyID)
p2 = ctypes.cast(buffer, ctypes.c_void_p)
ret_val = joyGetPosEx_func(p1, p2)
ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYINFOEX(buffer))
except:
ret = False, None
return ret
JOYERR_NOERROR = 0
JOY_RETURNX = 0x00000001
JOY_RETURNY = 0x00000002
JOY_RETURNZ = 0x00000004
JOY_RETURNR = 0x00000008
JOY_RETURNU = 0x00000010
JOY_RETURNV = 0x00000020
JOY_RETURNPOV = 0x00000040
JOY_RETURNBUTTONS = 0x00000080
JOY_RETURNRAWDATA = 0x00000100
JOY_RETURNPOVCTS = 0x00000200
JOY_RETURNCENTERED = 0x00000400
JOY_USEDEADZONE = 0x00000800
JOY_RETURNALL = (JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | \
JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | \
JOY_RETURNPOV | JOY_RETURNBUTTONS)
# joystickapi - JOYCAPS
class JOYCAPS:
SIZE_W = 728
OFFSET_V = 4 + 32*2
def __init__(self, buffer):
ushort_array = (ctypes.c_uint16 * 2).from_buffer(buffer)
self.wMid, self.wPid = ushort_array
wchar_array = (ctypes.c_wchar * 32).from_buffer(buffer, 4)
self.szPname = ctypes.cast(wchar_array, ctypes.c_wchar_p).value
uint_array = (ctypes.c_uint32 * 19).from_buffer(buffer, JOYCAPS.OFFSET_V)
self.wXmin, self.wXmax, self.wYmin, self.wYmax, self.wZmin, self.wZmax, \
self.wNumButtons, self.wPeriodMin, self.wPeriodMax, \
self.wRmin, self.wRmax, self.wUmin, self.wUmax, self.wVmin, self.wVmax, \
self.wCaps, self.wMaxAxes, self.wNumAxes, self.wMaxButtons = uint_array
# joystickapi - JOYINFOEX
class JOYINFOEX:
SIZE = 52
def __init__(self, buffer):
uint_array = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4)).from_buffer(buffer)
self.dwSize, self.dwFlags, \
self.dwXpos, self.dwYpos, self.dwZpos, self.dwRpos, self.dwUpos, self.dwVpos, \
self.dwButtons, self.dwButtonNumber, self.dwPOV, self.dwReserved1, self.dwReserved2 = uint_array
See the simple example to test the API:
import joystickapi
import msvcrt
import time
print("start")
num = joystickapi.joyGetNumDevs()
ret, caps, startinfo = False, None, None
for id in range(num):
ret, caps = joystickapi.joyGetDevCaps(id)
if ret:
print("gamepad detected: " + caps.szPname)
ret, startinfo = joystickapi.joyGetPosEx(id)
break
else:
print("no gamepad detected")
run = ret
while run:
time.sleep(0.1)
if msvcrt.kbhit() and msvcrt.getch() == chr(27).encode(): # detect ESC
run = False
ret, info = joystickapi.joyGetPosEx(id)
if ret:
btns = [(1 << i) & info.dwButtons != 0 for i in range(caps.wNumButtons)]
axisXYZ = [info.dwXpos-startinfo.dwXpos, info.dwYpos-startinfo.dwYpos, info.dwZpos-startinfo.dwZpos]
axisRUV = [info.dwRpos-startinfo.dwRpos, info.dwUpos-startinfo.dwUpos, info.dwVpos-startinfo.dwVpos]
if info.dwButtons:
print("buttons: ", btns)
if any([abs(v) > 10 for v in axisXYZ]):
print("axis:", axisXYZ)
if any([abs(v) > 10 for v in axisRUV]):
print("roation axis:", axisRUV)
print("end")
The api binding and example is provided in the GitHub repository python_windows_joystickapi