Created
February 4, 2018 13:15
-
-
Save wezu/9cd50bf57be046ec35603cb6a3499a60 to your computer and use it in GitHub Desktop.
Joystick support for P3D (windows olny)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Original release by rdb under the Unlicense (unlicense.org) | |
# Modified by wezu | |
from __future__ import print_function | |
from math import floor, ceil | |
import time | |
import ctypes | |
from ctypes.wintypes import WORD, UINT, DWORD | |
from ctypes.wintypes import WCHAR as TCHAR | |
from direct.showbase.DirectObject import DirectObject | |
# Fetch function pointers | |
joyGetNumDevs = ctypes.windll.winmm.joyGetNumDevs | |
joyGetPos = ctypes.windll.winmm.joyGetPos | |
joyGetPosEx = ctypes.windll.winmm.joyGetPosEx | |
joyGetDevCaps = ctypes.windll.winmm.joyGetDevCapsW | |
# Define constants | |
MAXPNAMELEN = 32 | |
MAX_JOYSTICKOEMVXDNAME = 260 | |
JOY_RETURNX = 0x1 | |
JOY_RETURNY = 0x2 | |
JOY_RETURNZ = 0x4 | |
JOY_RETURNR = 0x8 | |
JOY_RETURNU = 0x10 | |
JOY_RETURNV = 0x20 | |
JOY_RETURNPOV = 0x40 | |
JOY_RETURNBUTTONS = 0x80 | |
JOY_RETURNRAWDATA = 0x100 | |
JOY_RETURNPOVCTS = 0x200 | |
JOY_RETURNCENTERED = 0x400 | |
JOY_USEDEADZONE = 0x800 | |
JOY_RETURNALL = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | JOY_RETURNPOV | JOY_RETURNBUTTONS | |
# Define some structures from WinMM that we will use in function calls. | |
class JOYCAPS(ctypes.Structure): | |
_fields_ = [ | |
('wMid', WORD), | |
('wPid', WORD), | |
('szPname', TCHAR * MAXPNAMELEN), | |
('wXmin', UINT), | |
('wXmax', UINT), | |
('wYmin', UINT), | |
('wYmax', UINT), | |
('wZmin', UINT), | |
('wZmax', UINT), | |
('wNumButtons', UINT), | |
('wPeriodMin', UINT), | |
('wPeriodMax', UINT), | |
('wRmin', UINT), | |
('wRmax', UINT), | |
('wUmin', UINT), | |
('wUmax', UINT), | |
('wVmin', UINT), | |
('wVmax', UINT), | |
('wCaps', UINT), | |
('wMaxAxes', UINT), | |
('wNumAxes', UINT), | |
('wMaxButtons', UINT), | |
('szRegKey', TCHAR * MAXPNAMELEN), | |
('szOEMVxD', TCHAR * MAX_JOYSTICKOEMVXDNAME), | |
] | |
class JOYINFO(ctypes.Structure): | |
_fields_ = [ | |
('wXpos', UINT), | |
('wYpos', UINT), | |
('wZpos', UINT), | |
('wButtons', UINT), | |
] | |
class JOYINFOEX(ctypes.Structure): | |
_fields_ = [ | |
('dwSize', DWORD), | |
('dwFlags', DWORD), | |
('dwXpos', DWORD), | |
('dwYpos', DWORD), | |
('dwZpos', DWORD), | |
('dwRpos', DWORD), | |
('dwUpos', DWORD), | |
('dwVpos', DWORD), | |
('dwButtons', DWORD), | |
('dwButtonNumber', DWORD), | |
('dwPOV', DWORD), | |
('dwReserved1', DWORD), | |
('dwReserved2', DWORD), | |
] | |
povbtn_names = ['pad_up', 'pad_right', 'pad_down', 'pad_left'] | |
class Controller(DirectObject): | |
def __init__(self, controller_name='joystick'): | |
self.controller_name=controller_name | |
self.has_devices=False | |
# Get the number of supported devices (usually 16). | |
num_devs = joyGetNumDevs() | |
if num_devs == 0: | |
print("Joystick driver not loaded.") | |
return | |
# Number of the joystick to open. | |
joy_id = 0 | |
# Check if the joystick is plugged in. | |
joyinfo = JOYINFO() | |
p_info = ctypes.pointer(joyinfo) | |
if joyGetPos(0, p_info) != 0: | |
print("Joystick %d not plugged in." % (joy_id + 1)) | |
return | |
# Get device capabilities. | |
self.caps = JOYCAPS() | |
if joyGetDevCaps(joy_id, ctypes.pointer(self.caps), ctypes.sizeof(JOYCAPS)) != 0: | |
print("Failed to get device capabilities.") | |
return | |
self.has_devices=True | |
# Button states | |
self.last_button_states={'pad_up':False, 'pad_right':False, 'pad_down':False, 'pad_left':False} | |
for b in range(self.caps.wNumButtons): | |
name = 'button_'+str(b) | |
if (1 << b) & joyinfo.wButtons: | |
self.last_button_states[name] = True | |
else: | |
self.last_button_states[name] = False | |
self.last_analog={'x':0,'y':0,'rx':0,'ry':0,'lt':0, 'rt':0} | |
# Initialise the JOYINFOEX structure. | |
self.joyinfoex = JOYINFOEX() | |
self.joyinfoex.dwSize = ctypes.sizeof(JOYINFOEX) | |
self.joyinfoex.dwFlags = JOY_RETURNBUTTONS | JOY_RETURNCENTERED | JOY_RETURNPOV | JOY_RETURNU | JOY_RETURNV | JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | |
self.joyinfoex_pointer = ctypes.pointer(self.joyinfoex) | |
#task | |
taskMgr.add(self._update, 'controller._update') | |
def _update(self, task): | |
# Fetch new joystick data until it returns non-0 (that is, it has been unplugged) | |
if joyGetPosEx(0, self.joyinfoex_pointer) == 0: | |
# Analog stick/trigger | |
analog={} | |
# Remap the values to float | |
analog['x'] = (self.joyinfoex.dwXpos - 32767) / 32768.0 | |
analog['y'] = (self.joyinfoex.dwYpos - 32767) / 32768.0 | |
analog['rx'] = (self.joyinfoex.dwRpos - 32767) / 32768.0 | |
analog['ry'] = (self.joyinfoex.dwUpos - 32767) / 32768.0 | |
# NB. Windows drivers give one axis for the trigger, but I want to have | |
# two for compatibility with platforms that do support them as separate axes. | |
# This means it'll behave strangely when both triggers are pressed, though. | |
trig = (self.joyinfoex.dwZpos - 32767) / 32768.0 | |
analog['lt'] = max(-1.0, trig * 2 - 1.0) | |
analog['rt'] = max(-1.0, -trig * 2 - 1.0) | |
# Figure out which buttons are pressed. | |
button_states={} | |
for b in range(self.caps.wNumButtons): | |
pressed = (0 != (1 << b) & self.joyinfoex.dwButtons) | |
name = 'button_'+str(b) | |
button_states[name] = pressed | |
# Determine the state of the POV buttons using the provided POV angle. | |
if self.joyinfoex.dwPOV == 65535: | |
povangle1 = None | |
povangle2 = None | |
else: | |
angle = self.joyinfoex.dwPOV / 9000.0 | |
povangle1 = int(floor(angle)) % 4 | |
povangle2 = int(ceil(angle)) % 4 | |
for i, btn in enumerate(povbtn_names): | |
if i == povangle1 or i == povangle2: | |
button_states[btn] = True | |
else: | |
button_states[btn] = False | |
# Send events | |
for button, state in button_states.items(): | |
if self.last_button_states[button] != state: | |
if state is False: | |
messenger.send(button+'-up') | |
else: | |
messenger.send(button) | |
messenger.send(self.controller_name, [button]) | |
for axis, state in analog.items(): | |
if self.last_analog[axis] != state: | |
messenger.send(self.controller_name+'-analog', [axis, state]) | |
# Remember the state for next time | |
self.last_button_states=button_states | |
self.last_analog=analog | |
return task.cont | |
else: | |
self.has_devices=False | |
return task.done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment