Skip to content

Instantly share code, notes, and snippets.

@igor725
Created July 14, 2021 23:44
Show Gist options
  • Select an option

  • Save igor725/c10b9a01d4279691af08aa49fad652f9 to your computer and use it in GitHub Desktop.

Select an option

Save igor725/c10b9a01d4279691af08aa49fad652f9 to your computer and use it in GitHub Desktop.
Simple LuaJIT ffi bridge for xinput.dll
xinput = require('xinput').on()
xinput.iterStates(function(state)
print('Device ', state:getIndex(), 'is a', state:getType())
print('\tBattery info:', state:getBatteryInfo())
local caps = state:fetchCaps()
if caps then
print('\tCapabilities:')
for cap_name, cap_present in pairs(caps)do
io.write('\t\t', cap_name, ': ', (cap_present and'yes')or'no', '\r\n')
end
end
state:destroy()
end)
xinput = require('xinput').on()
xinput.iterStates(function(state)
if state:getType() ~= 'gamepad'then
state:destroy()
else
gamepad = state
return true
end
end)
local rate = 200 -- Updates per second
while gamepad:poll()do
io.write(('LT: %.3f, RT: %.3f, LS: (%.3f, %.3f), RS: (%.3f, %.3f), B: %d \r'):format(
gamepad.activity.left_trigger,
gamepad.activity.right_trigger,
gamepad.activity.left_thumb.x,
gamepad.activity.left_thumb.y,
gamepad.activity.right_thumb.x,
gamepad.activity.right_thumb.y,
gamepad.activity.buttons
))
xinput.sleep((1/rate)*1000)
end
--[[
Battery levels:
1.empty
2.low
3.medium
4.full
Battery types:
1.disconnected
2.wired
3.alkaine
4.nimh
5.unknown
Device types:
1.gamepad
2.wheel
3.arcade_stick
4.flight_stick
5.dance_pad
6.guitar
7.guitar_alt
8.drum_kit
9.guitar_bass
10.arcade_pad
11.unknown
Functions:
xinput.on(lib_name) - Load specified xinput library (or xinput 1.4 if lib_name is empty) and call XInputEnable(1)
xinput.off() - Call XInputEnable(0) and free loaded library
xinput.iterStates(func) - Iterate through all xinput devices with specified function
xinput.createState(index) - Returns state of a specified device
xinput.sleep(ms) - Sleep specified time in msecs
xinput.getLib() - Returns xinput.dll ffi representation and ffi table
xinput.poll(state)/state:poll() - Poll device updates
xinput.fetchCaps(state, force)/state:fetchCaps(force) - Read device capabilities
xinput.getCap(state, cap)/state:getCap(cap) - Check if the specified device has capability
xinput.getIndex(state)/state:getIndex() - Returns xinput device index
xinput.getType(state)/state:getType() - Returns xinput device type
xinput.getBatteryInfo(state, type)/state:getBatteryInfo(type) - Returns xinput device battery information
xinput.setVibration(state, left, right)/state:setVibration(left, right) - Set left and right motors speed in range [0,1]
xinput.getVibration(state)/state:getVibration() - Returns left and right motors speed
xinput.destroy(state)/state:destroy() - Destroy device state
Activity table:
left_trigger, right_trigger - value in range [0..1]
left_thumb, right_thumb - table with two values x and y in range [0..1]
buttons - bitmask of the pressed buttons
]]
local ffi = ((ffi and ffi.C) and ffi) or require('ffi')
local lib
local BATTERY_LEVELS = {
[0x00] = 'empty',
[0x01] = 'low',
[0x02] = 'medium',
[0x03] = 'full'
}
local BATTERY_TYPES = {
[0x00] = 'disconnected',
[0x01] = 'wired',
[0x02] = 'alkaine',
[0x03] = 'nimh',
[0x0FF] = 'unknown'
}
local DEVICE_TYPES = {
[0x01] = 'gamepad',
[0x02] = 'wheel',
[0x03] = 'arcade_stick',
[0x04] = 'flight_stick',
[0x05] = 'dance_pad',
[0x06] = 'guitar',
[0x07] = 'guitar_alt',
[0x08] = 'drum_kit',
[0x0B] = 'guitar_bass',
[0x13] = 'arcade_pad',
[0x00] = 'unknown'
}
ffi.cdef[[
typedef unsigned int DWORD;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef short SHORT;
struct xinput_gamepad {
WORD btns;
BYTE lt;
BYTE rt;
SHORT tLX;
SHORT tLY;
SHORT tRX;
SHORT tRY;
};
struct xinput_state {
DWORD num;
struct xinput_gamepad gamepad;
};
struct xinput_vibro {
WORD mLeft;
WORD mRight;
};
struct xinput_battery {
BYTE type;
BYTE level;
};
struct xinput_caps {
BYTE type;
BYTE subType;
WORD flags;
struct xinput_gamepad map;
struct xinput_vibro vib;
};
void Sleep(DWORD);
void XInputEnable(DWORD);
DWORD XInputGetState(DWORD, struct xinput_state*);
DWORD XInputSetState(DWORD, struct xinput_vibro*);
DWORD XInputGetCapabilities(DWORD, DWORD, struct xinput_caps*);
DWORD XInputGetBatteryInformation(DWORD, BYTE, struct xinput_battery*);
]]
local isset = function(flags, flag)return bit.band(flags, flag)==flag end
local M = {}
M.__index = M
function M.on(clib)
lib = ffi.load(clib or'xinput1_4.dll')
lib.XInputEnable(1)
return M
end
function M.off()
lib.XInputEnable(0)
lib = nil
end
function M.iterStates(func)
for i = 0, 3 do
if func(M.createState(i))then break end
end
end
function M.createState(index)
return setmetatable({
index = index,
activity = {
['left_trigger'] = 0,
['right_trigger'] = 0,
['left_thumb'] = {x = 0, y = 0},
['right_thumb'] = {x = 0, y = 0},
},
caps = {
['voice'] = false,
['ffb'] = false,
['wireless'] = false,
['pwd'] = false,
['nonav'] = false,
['two_motors'] = false,
['have_motor'] = false,
type = DEVICE_TYPES[0x00]
},
c_state = ffi.new('struct xinput_state'),
c_vibro = ffi.new('struct xinput_vibro'),
c_caps = ffi.new('struct xinput_caps'),
c_batt = ffi.new('struct xinput_battery')
}, M)
end
function M.poll(state)
local ret = lib.XInputGetState(state.index, state.c_state)
if ret == 0x48F then return false, -1 end
if ret ~= 0x000 then return false, ret end
state.activity.left_trigger = gamepad.c_state.gamepad.lt / 255
state.activity.right_trigger = gamepad.c_state.gamepad.rt / 255
state.activity.left_thumb.x = gamepad.c_state.gamepad.tLX / 32768
state.activity.left_thumb.y = gamepad.c_state.gamepad.tLY / 32768
state.activity.right_thumb.x = gamepad.c_state.gamepad.tRX / 32768
state.activity.right_thumb.y = gamepad.c_state.gamepad.tRY / 32768
state.activity.buttons = gamepad.c_state.gamepad.btns
return true
end
function M.fetchCaps(state, force)
if state.caps_fetched and not force then return state.caps end
local ret = lib.XInputGetCapabilities(state.index, 0, state.c_caps)
if ret == 0x48F then return false, -1 end
if ret ~= 0x000 then return false, ret end
state.caps.type = DEVICE_TYPES[state.c_caps.subType]
state.caps['voice'] = isset(state.c_caps.flags, 0x004)
state.caps['ffb'] = isset(state.c_caps.flags, 0x001)
state.caps['wireless'] = isset(state.c_caps.flags, 0x002)
state.caps['pwd'] = isset(state.c_caps.flags, 0x008)
state.caps['nonav'] = isset(state.c_caps.flags, 0x010)
state.caps['two_motors'] = state.c_caps.vib.mLeft == 0xFF and state.c_caps.vib.mLeft == 0xFF
state.caps['have_motor'] = state.c_caps.vib.mLeft == 0xFF or state.c_caps.vib.mLeft == 0xFF
state.caps_fetched = true
return state.caps
end
function M.getCap(state, cap)
state:fetchCaps()
return state.caps[cap]
end
function M.getIndex(state)
return state.index
end
function M.getType(state)
return state:getCap('type')
end
function M.getBatteryInfo(state, type)
local ret = lib.XInputGetBatteryInformation(state.index, type or 0x00, state.c_batt)
if ret == 0x48F then return false, 'disconnected'end
if ret ~= 0x000 then return false, ret end
return BATTERY_TYPES[state.c_batt.type], BATTERY_LEVELS[state.c_batt.level]
end
function M.setVibration(state, left, right)
state.c_vibro.mLeft = math.max(math.min(math.floor(left * 65535), 65535), 0)
state.c_vibro.mRight = math.max(math.min(math.floor(right * 65535), 65535), 0)
local ret = lib.XInputSetState(state.index, state.c_vibro)
return ret == 0x000, ret ~= 0x000 and (
(ret == 0x48F and -1)or
ret
) or nil
end
function M.getVibration(state)
return state.c_vibro.mLeft / 65535, state.c_vibro.mRight / 65535
end
function M.destroy(state)
setmetatable(state, nil)
state.c_state = nil
state.c_vibro = nil
state.c_caps = nil
state.caps = nil
state.index = nil
end
function M.sleep(msec)
ffi.C.Sleep(msec)
end
function M.getLib()
return lib, ffi
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment