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, "cmd", c_ulong, "data", c_char_p ] class PacketType: IOCTL_HID_SET_FEATURE = 0x0191 IOCTL_HID_WRITE_REPORT = 0x000F class FFBEType: Type = c_int ET_NONE = 0, # No Force ET_CONST = 1, # Constant Force ET_RAMP = 2, # Ramp ET_SQR = 3, # Square ET_SINE = 4, # Sine ET_TRNGL = 5, # Triangle ET_STUP = 6, # Sawtooth Up ET_STDN = 7, # Sawtooth Down ET_SPRNG = 8, # Spring ET_DMPR = 9, # Damper ET_INRT = 10, # Inertia ET_FRCTN = 11, # Friction ET_CSTM = 12, # Custom Force Data class FFBPType: Type = c_int PT_EFFREP = 0x01 # Usage Set Effect Report PT_ENVREP = 0x02 # Usage Set Envelope Report PT_CONDREP = 0x03 # Usage Set Condition Report 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 PT_BLKFRREP = 0x0B # Usage PID Block Free Report PT_CTRLREP = 0x0C # Usage PID Device Control PT_GAINREP = 0x0D # Usage Device Gain Report PT_SETCREP = 0x0E # Usage Set Custom Force Report PT_NEWEFREP = 0x11 # Usage Create New Effect Report PT_BLKLDREP = 0x12 # Usage Block Load Report PT_POOLREP = 0x13 # Usage PID Pool Report class FFBOP: Type = c_int EFF_START = 1 # EFFECT START EFF_SOLO = 2 # EFFECT SOLO START EFF_STOP = 3 # EFFECT STOP 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 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): _fields_ = [ # Polar direction: (0x00-0xFF correspond to 0-360�) ("Direction", wintypes.BYTE), # X direction: Positive values are To the right of the center (X); Negative are Two's complement ("DirX", wintypes.BYTE) ] class FFB_EFF_REPORT(Structure): _fields_ = [ ("EffectBlockIndex", wintypes.BYTE), ("EffectType", FFBEType.Type), # Value in milliseconds. 0xFFFF means infinite ("Duration", wintypes.WORD), ("TrigerRpt", wintypes.WORD), ("SamplePrd", wintypes.WORD), ("Gain", wintypes.BYTE), ("TrigerBtn", wintypes.BYTE), # How to interpret force direction Polar (0-360�) or Cartesian (X,Y) ("Polar", wintypes.BOOL), ("Direction", FFB_EFF_REPORT_Dir), # Y direction: Positive values are below the center (Y); Negative are Two's complement ("DirY", wintypes.BYTE) ] class FFB_EFF_OP(Structure): ('EffectBlockIndex', wintypes.BYTE), ('EffectOp', FFBOP), ('LoopCount', wintypes.BYTE) 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 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: def __init__(self, pointer): self.ptr = pointer def DeviceID(self): value = c_int(0) res = _vj.Ffb_h_DeviceID(self.ptr, byref(value)) return res, value def Type(self): value = FFBPType.Type(0) res = _vj.Ffb_h_Type(self.ptr, byref(value)) return res, value def Packet(self): pkt_type = wintypes.WORD(0) size = c_int(0) 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): value = c_int(0) res = _vj.Ffb_h_DeviceID(self.ptr, byref(value)) return res, value def Eff_Ramp(self): value = FFB_EFF_RAMP() res = _vj.Ffb_h_Eff_Ramp(self.ptr, byref(value)) return res, value def EffOp(self): value = FFB_EFF_OP(0) res = _vj.Ffb_h_EffOp(self.ptr, byref(value)) return res, value def DevCtrl(self): value = FFB_CTRL.Type(0) res = _vj.Ffb_h_DevCtrl(self.ptr, byref(value)) return res, value def Eff_Period(self): value = FFB_EFF_PERIOD() res = _vj.Ffb_h_Eff_Period(self.ptr, byref(value)) return res, value def Eff_Cond(self): value = FFB_EFF_COND() res = _vj.Ffb_h_Eff_Cond(self.ptr, byref(value)) return res, value def DevGain(self): value = wintypes.BYTE(0) res = _vj.Ffb_h_DevGain(self.ptr, byref(value)) return res, value def Eff_Envlp(self): value = FFB_EFF_ENVLP() res = _vj.Ffb_h_Eff_Envlp(self.ptr, byref(value)) return res, value def EffNew(self): value = FFBEType.Type(0) res = _vj.Ffb_h_EffNew(self.ptr, byref(value)) return res, value 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()) 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) _vj = vjoy_dev._vj _ffb_user_cb = ffb_cb _vj.FfbStart(vjoy_dev.rID) _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(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), 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), 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 _vj.Ffb_h_Packet.restype = wintypes.DWORD _vj.Ffb_h_EBI.restype = wintypes.DWORD _vj.Ffb_h_Eff_Report.restype = wintypes.DWORD _vj.Ffb_h_Eff_Ramp.restype = wintypes.DWORD _vj.Ffb_h_EffOp.restype = wintypes.DWORD _vj.Ffb_h_DevCtrl.restype = wintypes.DWORD _vj.Ffb_h_Eff_Period.restype = wintypes.DWORD _vj.Ffb_h_Eff_Cond.restype = wintypes.DWORD _vj.Ffb_h_DevGain.restype = wintypes.DWORD _vj.Ffb_h_Eff_Envlp.restype = wintypes.DWORD _vj.Ffb_h_EffNew.restype = wintypes.DWORD _vj.Ffb_h_Eff_Constant.restype = wintypes.DWORD