diff --git a/pyvjoy_ffb.py b/pyvjoy_ffb.py index caef1f0..d024d7b 100644 --- a/pyvjoy_ffb.py +++ b/pyvjoy_ffb.py @@ -2,9 +2,20 @@ import pyvjoy from ctypes import CFUNCTYPE, c_int, c_void_p, c_char_p, c_ulong, Structure, Union, POINTER, byref, wintypes _ffb_cb = None +_ffb_user_cb = None _vj = None +def enum_str(cls, value): + for k, v in cls.__dict__.items(): + if v == value: + return k + + +class ReturnCode: + SUCCESS = 0 + + class FfbPacket(Structure): _fields = [ "size", c_ulong, @@ -13,6 +24,11 @@ class FfbPacket(Structure): ] +class PacketType: + IOCTL_HID_SET_FEATURE = 0x0191 + IOCTL_HID_WRITE_REPORT = 0x000F + + class FFBEType: Type = c_int ET_NONE = 0, # No Force @@ -38,6 +54,7 @@ class FFBPType: PT_PRIDREP = 0x04 # Usage Set Periodic Report PT_CONSTREP = 0x05 # Usage Set Constant Force Report PT_RAMPREP = 0x06 # Usage Set Ramp Force Report + PT_CSTMREP = 0x07 # Usage Custom Force Data Report PT_SMPLREP = 0x08 # Usage Download Force Sample PT_EFOPREP = 0x0A # Usage Effect Operation Report @@ -46,58 +63,72 @@ class FFBPType: PT_GAINREP = 0x0D # Usage Device Gain Report PT_SETCREP = 0x0E # Usage Set Custom Force Report - PT_NEWEFREP = 0x01 # Usage Create New Effect Report - PT_BLKLDREP = 0x02 # Usage Block Load Report - PT_POOLREP = 0x03 # Usage PID Pool Report + PT_NEWEFREP = 0x11 # Usage Create New Effect Report + PT_BLKLDREP = 0x12 # Usage Block Load Report + PT_POOLREP = 0x13 # Usage PID Pool Report -# enum FFBOP -# { -# EFF_START = 1, # EFFECT START -# EFF_SOLO = 2, # EFFECT SOLO START -# EFF_STOP = 3, # EFFECT STOP -# }; -# enum FFB_CTRL -# { -# CTRL_ENACT = 1, # Enable all device actuators. -# CTRL_DISACT = 2, # Disable all the device actuators. -# CTRL_STOPALL = 3, # Stop All Effects� Issues a stop on every running effect. -# CTRL_DEVRST = 4, # Device Reset� Clears any device paused condition, enables all actuators and clears all effects from memory. -# CTRL_DEVPAUSE = 5, # Device Pause� The all effects on the device are paused at the current time step. -# CTRL_DEVCONT = 6, # Device Continue� The all effects that running when the device was paused are restarted from their last time step. -# }; +class FFBOP: + Type = c_int + EFF_START = 1 # EFFECT START + EFF_SOLO = 2 # EFFECT SOLO START + EFF_STOP = 3 # EFFECT STOP -# enum FFB_EFFECTS { -# Constant = 0x0001, -# Ramp = 0x0002, -# Square = 0x0004, -# Sine = 0x0008, -# Triangle = 0x0010, -# Sawtooth_Up = 0x0020, -# Sawtooth_Dn = 0x0040, -# Spring = 0x0080, -# Damper = 0x0100, -# Inertia = 0x0200, -# Friction = 0x0400, -# Custom = 0x0800, -# }; -# typedef struct _FFB_DATA { -# ULONG size; -# ULONG cmd; -# UCHAR *data; -# } FFB_DATA, * PFFB_DATA; +class FFB_CTRL: + Type = c_int + CTRL_ENACT = 1 # Enable all device actuators. + CTRL_DISACT = 2 # Disable all the device actuators. + # Stop All Effects. Issues a stop on every running effect. + CTRL_STOPALL = 3 + # Device Reset� Clears any device paused condition, enables all actuators and clears all effects from memory. + CTRL_DEVRST = 4 + # Device Pause� The all effects on the device are paused at the current time step. + CTRL_DEVPAUSE = 5 + # Device Continue� The all effects that running when the device was paused are restarted from their last time step. + CTRL_DEVCONT = 6 -# typedef struct _FFB_EFF_CONSTANT { -# BYTE EffectBlockIndex; -# LONG Magnitude; # Constant force magnitude: -10000 - 10000 -# } FFB_EFF_CONSTANT, *PFFB_EFF_CONSTANT; -# typedef struct _FFB_EFF_RAMP { -# BYTE EffectBlockIndex; -# LONG Start; # The Normalized magnitude at the start of the effect (-10000 - 10000) -# LONG End; # The Normalized magnitude at the end of the effect (-10000 - 10000) -# } FFB_EFF_RAMP, *PFFB_EFF_RAMP; +class FFB_EFFECTS: + Type = c_int + Constant = 0x0001 + Ramp = 0x0002 + Square = 0x0004 + Sine = 0x0008 + Triangle = 0x0010 + Sawtooth_Up = 0x0020 + Sawtooth_Dn = 0x0040 + Spring = 0x0080 + Damper = 0x0100 + Inertia = 0x0200 + Friction = 0x0400 + Custom = 0x0800 + + +class FFB_DATA(Structure): + _fields_ = [ + ('size', wintypes.ULONG), + ('cmd', wintypes.ULONG), + ('data', c_char_p) + ] + + +class FFB_EFF_CONSTANT(Structure): + _fields_ = [ + ('EffectBlockIndex', wintypes.BYTE), + # Constant force magnitude: -10000 - 10000 + ('Magnitude', wintypes.LONG) + ] + + +class FFB_EFF_RAMP(Structure): + _fields_ = [ + ('EffectBlockIndex', wintypes.BYTE), + # The Normalized magnitude at the start of the effect (-10000 - 10000) + ('Start', wintypes.LONG), + # The Normalized magnitude at the end of the effect (-10000 - 10000) + ('End', wintypes.LONG) + ] class FFB_EFF_REPORT_Dir(Union): @@ -113,7 +144,8 @@ class FFB_EFF_REPORT(Structure): _fields_ = [ ("EffectBlockIndex", wintypes.BYTE), ("EffectType", FFBEType.Type), - ("Duration", wintypes.WORD), # Value in milliseconds. 0xFFFF means infinite + # Value in milliseconds. 0xFFFF means infinite + ("Duration", wintypes.WORD), ("TrigerRpt", wintypes.WORD), ("SamplePrd", wintypes.WORD), ("Gain", wintypes.BYTE), @@ -125,55 +157,44 @@ class FFB_EFF_REPORT(Structure): ("DirY", wintypes.BYTE) ] -# typedef struct _FFB_EFF_REPORT { -# BYTE ; -# ; -# WORD ;# -# WORD ; -# WORD ; -# BYTE ; -# BYTE ; -# BOOL ; -# union -# { -# BYTE Direction; -# BYTE DirX; -# }; -# BYTE DirY; -# } FFB_EFF_REPORT, *PFFB_EFF_REPORT; -# typedef struct _FFB_EFF_OP { -# BYTE EffectBlockIndex; -# FFBOP EffectOp; -# BYTE LoopCount; -# } FFB_EFF_OP, *PFFB_EFF_OP; +class FFB_EFF_OP(Structure): + ('EffectBlockIndex', wintypes.BYTE), + ('EffectOp', FFBOP), + ('LoopCount', wintypes.BYTE) -# typedef struct _FFB_EFF_PERIOD { -# BYTE EffectBlockIndex; -# DWORD Magnitude; # Range: 0 - 10000 -# LONG Offset; # Range: �10000 - 10000 -# DWORD Phase; # Range: 0 - 35999 -# DWORD Period; # Range: 0 - 32767 -# } FFB_EFF_PERIOD, *PFFB_EFF_PERIOD; -# typedef struct _FFB_EFF_COND { -# BYTE EffectBlockIndex; -# BOOL isY; -# LONG CenterPointOffset; # CP Offset: Range -�10000 �- 10000 -# LONG PosCoeff; # Positive Coefficient: Range -�10000 �- 10000 -# LONG NegCoeff; # Negative Coefficient: Range -�10000 �- 10000 -# DWORD PosSatur; # Positive Saturation: Range 0 � 10000 -# DWORD NegSatur; # Negative Saturation: Range 0 � 10000 -# LONG DeadBand; # Dead Band: : Range 0 � 1000 -# } FFB_EFF_COND, *PFFB_EFF_COND; +class FFB_EFF_PERIOD(Structure): + ('EffectBlockIndex', wintypes.BYTE), + ('Magnitude', wintypes.DWORD), # Range: 0 - 10000 + ('Offset', wintypes.LONG), # Range: �10000 - 10000 + ('Phase', wintypes.DWORD), # Range: 0 - 35999 + ('Period', wintypes.DWORD) # Range: 0 - 32767 -# typedef struct _FFB_EFF_ENVLP { -# BYTE EffectBlockIndex; -# DWORD AttackLevel; # The Normalized magnitude of the stating point: 0 - 10000 -# DWORD FadeLevel; # The Normalized magnitude of the stopping point: 0 - 10000 -# DWORD AttackTime; # Time of the attack: 0 - 4294967295 -# DWORD FadeTime; # Time of the fading: 0 - 4294967295 -# } FFB_EFF_ENVLP, *PFFB_EFF_ENVLP; + +class FFB_EFF_COND(Structure): + ('EffectBlockIndex', wintypes.BYTE), + ('isY', wintypes.BOOL), + ('CenterPointOffset', wintypes.LONG), # CP Offset: Range -�10000 �- 10000 + ('PosCoeff', wintypes.LONG), # Positive Coefficient: Range -�10000 �- 10000 + ('NegCoeff', wintypes.LONG), # Negative Coefficient: Range -�10000 �- 10000 + ('PosSatur', wintypes.DWORD), # Positive Saturation: Range 0 � 10000 + ('NegSatur', wintypes.DWORD), # Negative Saturation: Range 0 � 10000 + ('DeadBand', wintypes.LONG), # Dead Band: : Range 0 � 1000 + + +class FFB_EFF_ENVLP(Structure): + ('EffectBlockIndex', wintypes.BYTE), + # The Normalized magnitude of the stating point: 0 - 10000 + ('AttackLevel', wintypes.DWORD), + # The Normalized magnitude of the stopping point: 0 - 10000 + ('FadeLevel', wintypes.DWORD), + ('AttackTime', wintypes.DWORD), # Time of the attack: 0 - 4294967295 + ('FadeTime', wintypes.DWORD) # Time of the fading: 0 - 4294967295 + + +MaxMemorySize = 200 +BufferType = wintypes.BYTE * MaxMemorySize class FfbPacketHelper: @@ -181,115 +202,203 @@ class FfbPacketHelper: self.ptr = pointer def DeviceID(self): - devid = c_int(0) - res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - return res, devid + value = c_int(0) + res = _vj.Ffb_h_DeviceID(self.ptr, byref(value)) + return res, value def Type(self): - ffbtype = FFBPType.Type(0) - res = _vj.Ffb_h_Type(self.ptr, byref(ffbtype)) - return res, ffbtype + value = FFBPType.Type(0) + res = _vj.Ffb_h_Type(self.ptr, byref(value)) + return res, value - # def Packet(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Packet(self): + pkt_type = wintypes.WORD(0) + size = c_int(0) - # def EBI(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + data = BufferType() + + res = _vj.Ffb_h_Packet(self.ptr, byref( + pkt_type), byref(size), byref(data)) + return res, pkt_type, size, data + + def EBI(self): + value = c_int(0) + res = _vj.Ffb_h_EBI(self.ptr, byref(value)) + return res, value def Eff_Report(self): - devid = c_int(0) - res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - return res, devid + value = c_int(0) + res = _vj.Ffb_h_DeviceID(self.ptr, byref(value)) + return res, value - # def Eff_Const(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Eff_Ramp(self): + value = FFB_EFF_RAMP() + res = _vj.Ffb_h_Eff_Ramp(self.ptr, byref(value)) + return res, value - # def Eff_Ramp(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def EffOp(self): + value = FFB_EFF_OP(0) + res = _vj.Ffb_h_EffOp(self.ptr, byref(value)) + return res, value - # def EffOp(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def DevCtrl(self): + value = FFB_CTRL.Type(0) + res = _vj.Ffb_h_DevCtrl(self.ptr, byref(value)) + return res, value - # def DevCtrl(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Eff_Period(self): + value = FFB_EFF_PERIOD() + res = _vj.Ffb_h_Eff_Period(self.ptr, byref(value)) + return res, value - # def Eff_Period(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Eff_Cond(self): + value = FFB_EFF_COND() + res = _vj.Ffb_h_Eff_Cond(self.ptr, byref(value)) + return res, value - # def Eff_Cond(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def DevGain(self): + value = wintypes.BYTE(0) + res = _vj.Ffb_h_DevGain(self.ptr, byref(value)) + return res, value - # def DevGain(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Eff_Envlp(self): + value = FFB_EFF_ENVLP() + res = _vj.Ffb_h_Eff_Envlp(self.ptr, byref(value)) + return res, value - # def Eff_Envlp(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def EffNew(self): + value = FFBEType.Type(0) + res = _vj.Ffb_h_EffNew(self.ptr, byref(value)) + return res, value - # def EffNew(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid - - # def Eff_Constant(self): - # devid = c_int(0) - # res = _vj.Ffb_h_DeviceID(self.ptr, byref(devid)) - # return res, devid + def Eff_Constant(self): + value = FFB_EFF_CONSTANT() + res = _vj.Ffb_h_Eff_Constant(self.ptr, byref(value)) + return res, value def pyvjoy_ffb_default_cb(ffb_pkt_p, userdata): p = FfbPacketHelper(ffb_pkt_p) # print(p.DeviceID()) - print(p.Type()) + res, ffb_type = p.Type() + + if res != ReturnCode.SUCCESS: + print("Got invalid Packet!") + return + + ffb_type = ffb_type.value + + res = ReturnCode.SUCCESS + value = None + if ffb_type == FFBPType.PT_EFFREP: + res, value = p.Eff_Report() + elif ffb_type == FFBPType.PT_ENVREP: + res, value = p.Eff_Envlp() + elif ffb_type == FFBPType.PT_CONDREP: + res, value = p.Eff_Cond() + elif ffb_type == FFBPType.PT_PRIDREP: + res, value = p.Eff_Period() + elif ffb_type == FFBPType.PT_CONSTREP: + res, value = p.Eff_Constant() + elif ffb_type == FFBPType.PT_RAMPREP: + res, value = p.Eff_Ramp() + elif ffb_type == FFBPType.PT_CSTMREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_SMPLREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_EFOPREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_BLKFRREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_CTRLREP: + res, value = p.DevCtrl() + elif ffb_type == FFBPType.PT_GAINREP: + res, value = p.DevGain() + elif ffb_type == FFBPType.PT_SETCREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_NEWEFREP: + res, value = p.EffNew() + elif ffb_type == FFBPType.PT_BLKLDREP: + print('not implemented: %02x'%ffb_type) + elif ffb_type == FFBPType.PT_POOLREP: + print('not implemented: %02x'%ffb_type) + + print('[%-20s/%02x:%08x]: %s' % + (enum_str(FFBPType, ffb_type), ffb_type, res, str(value))) + + if res != ReturnCode.SUCCESS: + print("Got invalid Packet!") + return + + if _ffb_user_cb: + _ffb_user_cb(ffb_type, value, p) + +# FFB_CTRL +# FFB_DATA +# FFB_EFF_COND +# FFB_EFF_CONSTANT +# FFB_EFF_ENVLP +# FFB_EFF_OP +# FFB_EFF_PERIOD +# FFB_EFF_RAMP +# FFB_EFF_REPORT_Dir +# FFB_EFF_REPORT +# FFB_EFFECTS +# FFBEType +# FFBOP +# FfbPacket +# FfbPacketHelper +# FFBPType + + # res, pkt_type, size, data = p.Packet() + + # if pkt_type == PacketType.IOCTL_HID_SET_FEATURE: + # # PT_NEWEFREP = 0x01 # Usage Create New Effect Report + # # PT_BLKLDREP = 0x02 # Usage Block Load Report + # # PT_POOLREP = 0x03 # Usage PID Pool Report + # pass + # elif pkt_type == PacketType.IOCTL_HID_WRITE_REPORT: + # pass + + # print('Pkt: %02x [%04x, % 3d: %s]' % + # (ffb_type.value, pkt_type.value, size.value, str(bytes(data)))) + # del data # print(' '.join('%02x' % x for x in ffb_pkt[0])) def add_ffb(vjoy_dev, ffb_cb=None): global _ffb_cb, _vj PYVJOY_CB_FUNC = CFUNCTYPE(None, POINTER(FfbPacket), c_void_p) - if not ffb_cb: - ffb_cb = pyvjoy_ffb_default_cb _vj = vjoy_dev._vj + _ffb_user_cb = ffb_cb _vj.FfbStart(vjoy_dev.rID) - _ffb_cb = PYVJOY_CB_FUNC(ffb_cb) + _ffb_cb = PYVJOY_CB_FUNC(pyvjoy_ffb_default_cb) _vj.FfbRegisterGenCB(_ffb_cb, 0) + _vj.Ffb_h_DeviceID.argtypes = [POINTER(FfbPacket), POINTER(c_int)] _vj.Ffb_h_Type.argtypes = [POINTER(FfbPacket), POINTER(FFBPType.Type)] _vj.Ffb_h_Packet.argtypes = [POINTER(FfbPacket), POINTER( - wintypes.WORD), POINTER(c_int), POINTER(wintypes.BYTE)] + wintypes.WORD), POINTER(c_int), POINTER(BufferType)] _vj.Ffb_h_EBI.argtypes = [POINTER(FfbPacket), POINTER(c_int)] - _vj.Ffb_h_Eff_Report.argtypes = [POINTER(FfbPacket), POINTER(FFB_EFF_REPORT)] - # _vj.Ffb_h_Eff_Ramp.argtypes = [POINTER(FfbPacket), FFB_EFF_RAMP* RampEffect] - # _vj.Ffb_h_EffOp.argtypes = [POINTER(FfbPacket), FFB_EFF_OP* Operation] - # _vj.Ffb_h_DevCtrl.argtypes = [POINTER(FfbPacket), FFB_CTRL * Control] - # _vj.Ffb_h_Eff_Period.argtypes = [POINTER(FfbPacket), FFB_EFF_PERIOD* Effect] - # _vj.Ffb_h_Eff_Cond.argtypes = [POINTER(FfbPacket), FFB_EFF_COND* Condition] + _vj.Ffb_h_Eff_Report.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_REPORT)] + _vj.Ffb_h_Eff_Ramp.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_RAMP)] + _vj.Ffb_h_EffOp.argtypes = [POINTER(FfbPacket), POINTER(FFB_EFF_OP)] + _vj.Ffb_h_DevCtrl.argtypes = [POINTER(FfbPacket), POINTER(FFB_CTRL.Type)] + _vj.Ffb_h_Eff_Period.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_PERIOD)] + _vj.Ffb_h_Eff_Cond.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_COND)] _vj.Ffb_h_DevGain.argtypes = [POINTER(FfbPacket), POINTER(wintypes.BYTE)] - # _vj.Ffb_h_Eff_Envlp.argtypes = [POINTER(FfbPacket), FFB_EFF_ENVLP* Envelope] - # _vj.Ffb_h_EffNew.argtypes = [POINTER(FfbPacket), FFBEType * Effect] - # _vj.Ffb_h_Eff_Constant.argtypes = [POINTER(FfbPacket), FFB_EFF_CONSTANT * ConstantEffect] + _vj.Ffb_h_Eff_Envlp.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_ENVLP)] + _vj.Ffb_h_EffNew.argtypes = [POINTER(FfbPacket), POINTER(FFBEType.Type)] + _vj.Ffb_h_Eff_Constant.argtypes = [ + POINTER(FfbPacket), POINTER(FFB_EFF_CONSTANT)] _vj.Ffb_h_DeviceID.restype = wintypes.DWORD _vj.Ffb_h_Type.restype = wintypes.DWORD @@ -305,32 +414,3 @@ def add_ffb(vjoy_dev, ffb_cb=None): _vj.Ffb_h_Eff_Envlp.restype = wintypes.DWORD _vj.Ffb_h_EffNew.restype = wintypes.DWORD _vj.Ffb_h_Eff_Constant.restype = wintypes.DWORD - - -# VJOYINTERFACE_API VOID __cdecl FfbRegisterGenCB(FfbGenCB cb, PVOID data); -# Register a FFB callback function that will be called by the driver every time a FFB data packet arrives. For additional -# information see Receptor Unit section. -# VJOYINTERFACE_API BOOL __cdecl FfbStart(UINT rID); -# Enable the FFB mechanism of the specified VDJ. -# Return TRUE on success. Otherwise return FALSE. -# VJOYINTERFACE_API VOID __cdecl FfbStop(UINT rID); -# Disable the FFB mechanism of the specified VDJ. -# [NEW] -# VJOYINTERFACE_API BOOL __cdecl IsDeviceFfb(UINT rID); -# Return TRUE if specified device supports FFB. Otherwise return FALSE. -# [NEW] -# VJOYINTERFACE_API BOOL __cdecl IsDeviceFfbEffect(UINT rID, UINT Effect) -# Return TRUE if specified device supports a specific FFB Effect. Otherwise return FALSE. -# The FFB Effect is indicated by its Usage. -# List of effect Usages: -# HID_USAGE_CONST (0x26): Usage ET Constant Force -# HID_USAGE_RAMP (0x27): Usage ET Ramp -# HID_USAGE_SQUR (0x30): Usage ET Square -# HID_USAGE_SINE (0x31): Usage ET Sine -# HID_USAGE_TRNG (0x32): Usage ET Triangle -# HID_USAGE_STUP (0x33): Usage ET Sawtooth Up -# HID_USAGE_STDN (0x34): Usage ET Sawtooth Down -# HID_USAGE_SPRNG (0x40): Usage ET Spring -# HID_USAGE_DMPR (0x41): Usage ET Damper -# HID_USAGE_INRT (0x42): Usage ET Inertia -# HID_USAGE_FRIC (0x43): Usage ET Friction