Using XBOX 360 joypads in Panda3d

It turns out that it is quite easy to use an XBOX 360 joypad in Panda3d on Windows using no extra modules by just making use of the ctypes modules which are part of the normal Python install. I am no expert, I found this technique at https://gist.github.com/dcoles/4378653 on github and just wanted to share it. The code sample below to the best of my knowledge runs on Panda 1.6 and later and can be saved as a .py file and run as a Panda3d program to demonstrate how you can read the joypad’s status and make it vibrate.

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

import ctypes
from ctypes import *
from ctypes.wintypes import BYTE, WORD, SHORT, DWORD, SHORT
UBYTE = c_ubyte

class XInputGamepad(ctypes.Structure):
    _fields_ = [
        ("buttons", WORD),
        ("leftTrigger", UBYTE),
        ("rightTrigger", UBYTE),
        ("thumbLX", SHORT),
        ("thumbLY", SHORT),
        ("thumbRX", SHORT),
        ("thumbRY", SHORT),
    ]
    def __repr__(self):
        return "XInputGamepad(thumbLX={0}, thumbRX={1}, ...)".format(self.thumbLX, self.thumbRX)
        
class XInputState(ctypes.Structure):
    _fields_ = [
        ("packetNumber", DWORD),
        ("gamepad", XInputGamepad),
    ]

    def __repr__(self):
        return "XInputState(packetNumber={0})".format(self.packetNumber)

class XInputVibration(ctypes.Structure):
    _fields_=[
        ("leftMotorSpeed",WORD),
        ("rightMotorSpeed",WORD),
    ]
    def __init__(self,leftSpeed,rightSpeed):
        self.leftMotorSpeed=leftSpeed
        self.rightMotorSpeed=rightSpeed


class Main(DirectObject):

    def __init__(self):
        self.XINPUT_GAMEPAD_DPAD_UP	        =0x0001
        self.XINPUT_GAMEPAD_DPAD_DOWN	        =0x0002
        self.XINPUT_GAMEPAD_DPAD_LEFT	        =0x0004
        self.XINPUT_GAMEPAD_DPAD_RIGHT          =0x0008
        self.XINPUT_GAMEPAD_START	        =0x0010
        self.XINPUT_GAMEPAD_BACK	        =0x0020
        self.XINPUT_GAMEPAD_LEFT_THUMB	        =0x0040
        self.XINPUT_GAMEPAD_RIGHT_THUMB	        =0x0080
        self.XINPUT_GAMEPAD_LEFT_SHOULDER	=0x0100
        self.XINPUT_GAMEPAD_RIGHT_SHOULDER	=0x0200
        self.XINPUT_GAMEPAD_A	                =0x1000
        self.XINPUT_GAMEPAD_B	                =0x2000
        self.XINPUT_GAMEPAD_X	                =0x4000
        self.XINPUT_GAMEPAD_Y	                =0x8000

        base.setBackgroundColor(0.6, 0.6, 0.8)

        self.xinput = ctypes.windll.xinput9_1_0

        self.onScreenTextA=OnscreenText(text = "", pos = (0,0.9), scale = 0.1, mayChange = 1)
        self.onScreenText0=OnscreenText(text = "", pos = (0,0.6), scale = 0.1, mayChange = 1)
        self.onScreenText1=OnscreenText(text = "", pos = (0,0.5), scale = 0.1, mayChange = 1)
        self.onScreenText2=OnscreenText(text = "", pos = (0,0.4), scale = 0.1, mayChange = 1)
        self.onScreenText3=OnscreenText(text = "", pos = (0,0.3), scale = 0.1, mayChange = 1)
        self.onScreenText4=OnscreenText(text = "", pos = (0,0.2), scale = 0.1, mayChange = 1)
        self.onScreenText5=OnscreenText(text = "", pos = (0,0.1), scale = 0.1, mayChange = 1)
        self.onScreenText6=OnscreenText(text = "", pos = (0,0.0), scale = 0.1, mayChange = 1)
        self.onScreenText7=OnscreenText(text = "", pos = (0,-0.1), scale = 0.1, mayChange = 1)
        self.onScreenText8=OnscreenText(text = "", pos = (0,-0.2), scale = 0.1, mayChange = 1)
        self.onScreenText9=OnscreenText(text = "", pos = (0,-0.3), scale = 0.1, mayChange = 1)
        self.onScreenText10=OnscreenText(text = "", pos = (0,-0.4), scale = 0.1, mayChange = 1)                                

        taskMgr.add(self.countPads,"countPads")
        

    def howManyPadsConnected(self):
        pads=0
        for x in range(0,4):
         state = XInputState(0)
         res = self.xinput.XInputGetState(x, ctypes.byref(state))
         if res==0:
             pads+=1
        return pads
    
    def getState(self,index):
     state = XInputState(0)
     res = self.xinput.XInputGetState(index, ctypes.byref(state))
     if res != 0:
      raise ctypes.WinError(res)
     return state.gamepad

    def setState(self,index,state):
         res = self.xinput.XInputSetState(index, ctypes.byref(state))
         if res != 0:
          raise ctypes.WinError(res)

    def countPads(self,task):
        if self.howManyPadsConnected()<1:
            self.onScreenText0.setText("connect at least one pad")
        else:
            taskMgr.add(self.mainTask,"mainTask")
            taskMgr.remove("countPads")

        return Task.cont
        
    def mainTask(self, task):

        self.onScreenTextA.setText(str(self.howManyPadsConnected())+" pads are connected")

        #get the state of the first joypad (the state holds information on the
        #position of the joysticks, triggers and which buttons are pressed)
        state=self.getState(0)
        self.onScreenText0.setText("pad 0:")
        self.onScreenText1.setText("buttons= "+str(state.buttons))
        self.onScreenText2.setText("left trigger= "+str(state.leftTrigger))
        self.onScreenText3.setText("right trigger= "+str(state.rightTrigger))
        self.onScreenText4.setText("left thumb stick x-axis= "+str(state.thumbLX))
        self.onScreenText5.setText("left thumb stick y-axis= "+str(state.thumbLY))
        self.onScreenText6.setText("right thumb stick x-axis= "+str(state.thumbRX))
        self.onScreenText7.setText("right thumb stick y-axis= "+str(state.thumbRY))                        

        if state.buttons & self.XINPUT_GAMEPAD_A :
            self.onScreenText8.setText("A is pressed")
            self.setState(0, XInputVibration(0,65534))
        else:
            self.onScreenText8.setText("press A to make right motor vibrate")
                
        if state.buttons & self.XINPUT_GAMEPAD_B :
            self.onScreenText9.setText("B is pressed")
            self.setState(0, XInputVibration(65534,0))
        else:
            self.onScreenText9.setText("press B to make left motor vibrate")

        if state.buttons & self.XINPUT_GAMEPAD_X :
            self.onScreenText10.setText("X is pressed")
            self.setState(0, XInputVibration(0,0))
        else:
            self.onScreenText10.setText("press X to stop vibrations")
                        
        return Task.cont
    
    
main = Main()
run()