Created
July 14, 2021 23:44
-
-
Save igor725/c10b9a01d4279691af08aa49fad652f9 to your computer and use it in GitHub Desktop.
Simple LuaJIT ffi bridge for xinput.dll
This file contains hidden or 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
| 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) |
This file contains hidden or 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
| 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 |
This file contains hidden or 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
| --[[ | |
| 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