About
Access native XInput functions as well as some helpers based around them.
This lib hooks directly to the system's dll (xinput1_4.dll, xinput1_3.dll or xinput9_1_0.dll).
It aims to implement and expose XInput functions as close as possible to the document.
🔍 "Hidden" XInput functions such as XInputGetCapabilitiesEx()
are exposed as well.
Examples
Vibration via helper function
import { rumble } from "xinput-ffi";
//Rumble 1st XInput gamepad
await rumble();
//Now with 100% force
await rumble({force: 100});
//low-frequency rumble motor(left) at 50%
//and high-frequency rumble motor (right) at 25%
await rumble({force: [50,25]});
XInput function
import * as XInput from "xinput-ffi";
const capabilities = await XInput.getCapabilities({translate: true});
console.log(capabilities);
/* Output:
{
type: 'XINPUT_DEVTYPE_GAMEPAD',
subType: 'XINPUT_DEVSUBTYPE_GAMEPAD',
flags: [ 'XINPUT_CAPS_VOICE_SUPPORTED', 'XINPUT_CAPS_PMD_SUPPORTED' ],
gamepad: {
wButtons: [
'XINPUT_GAMEPAD_DPAD_UP',
'XINPUT_GAMEPAD_DPAD_DOWN',
//etc...
],
bLeftTrigger: 255,
bRightTrigger: 255,
sThumbLX: -64,
sThumbLY: -64,
sThumbRX: -64,
sThumbRY: -64
},
vibration: { wLeftMotorSpeed: 255, wRightMotorSpeed: 255 }
}
*/
"Hidden" XInput function
import * as XInput from "xinput-ffi";
const state = await XInput.getStateEx();
console.log(state);
/*Output:
{
dwPacketNumber: 6510,
gamepad: {
wButtons: [ 'XINPUT_GAMEPAD_GUIDE' ],
bLeftTrigger: 0,
bRightTrigger: 0,
sThumbLX: -1024,
sThumbLY: 767,
sThumbRX: 257,
sThumbRY: 767
}
}
*/
Miscellaneous
import * as XInput from "xinput-ffi";
//Check connected status for all controller
console.log(await XInput.listConnected());
// [true,false,false,false] Only 1st gamepad is connected
//Identify connected XInput devices
console.log (await XInput.identify({XInputOnly: true}));
/* Output:
[
{
name: 'Xbox360 Controller',
manufacturer: 'Microsoft Corp.',
vendorID: 1118,
productID: 654,
xinput: true,
interfaces: [ 'USB', 'HID' ],
guid: [
'{745a17a0-74d3-11d0-b6fe-00a0c90f57da}',
'{d61ca365-5af4-4486-998b-9db4734c6ca3}'
]
}
]
*/
Electron
Simple XInput menu navigation
Here is an example of a simple XInput menu navigation system using the high level XInput implementation found in this module (helper).
- main process
let gamepad;
mainWin.once("ready-to-show", async() => {
const { XInputGamepad } = await import("xinput-ffi");
gamepad = new XInputGamepad();
//send input to renderer
gamepad.on("input", (buttons)=>{
setImmediate(() => {
mainWin.webContents.send("onGamepadInput", buttons);
});
});
gamepad.poll(); //gamepad event loop
mainWin.show();
mainWin.focus();
});
//gain/loose focus
mainWin.on("blur", () => {
gamepad?.pause();
});
mainWin.on("focus", () => {
gamepad?.resume();
});
//clean up
mainWin.on("close", () => {
gamepad?.stop();
gamepad = null; //deref
});
mainWin.on("closed", () => {
mainWin = null; //deref
});
mainWin.loadFile(path/to/file);
- contextBridge (preload)
contextBridge.exposeInMainWorld("ipcRenderer", {
onGamepadInput: (callback) => ipcRenderer.on("onGamepadInput", callback)
});
- renderer
window.ipcRenderer.onGamepadInput((event, input) => {
switch(input[0]){
case "XINPUT_GAMEPAD_DPAD_UP":
//do something
break;
default:
console.log(input);
}
});
Installation
npm install xinput-ffi
API
Previous version(s) are CommonJS (CJS) with an ESM wrapper.
Named export
Summary:
- constants
- XInput function
- Helper functions
- Identify device | VID/PID
- High level implementation of XInput
const constants = object
XInput controller constants for convenience.
import { constants } from "xinput-ffi";
console.log(constants.XUSER_MAX_COUNT); //4
💡 Also available under its own namespace.
import { XUSER_MAX_COUNT } from "xinput-ffi/constants";
console.log(XUSER_MAX_COUNT); //4
XInput function
- ✔️ XInputEnable
- ❌ XInputGetAudioDeviceIds > deprecated: doesn't work on modern Windows system.
- ✔️ XInputGetBatteryInformation
- ✔️ XInputGetCapabilities
- ❌ XInputGetDSoundAudioDeviceGuids > deprecated: doesn't work on modern Windows system.
- ✔️ XInputGetKeystroke
- ✔️ XInputGetState
- ✔️ XInputSetState
"Hidden" and undocumented functions
📖 Reverse Engineer's log
- ✔️ XInputGetStateEx
- ✔️ XInputWaitForGuideButton
- ✔️ XInputCancelGuideButtonWait
- ✔️ XInputPowerOffController
⚠️ XInputGetBaseBusInformation > Not working with all gamepad.- ✔️ XInputGetCapabilitiesEx
NB: Depending on which XInput dll version you are using (1_4, 1_3, 9_1_0) some functions won't be available.
enable(enable: boolean): Promise<void>
Enable/Disable all XInput gamepads.
This function is meant to be called when an application gains or loses focus.
NB:
- Stop any rumble currently playing when set to false.
- This may trigger
ERR_DEVICE_NOT_CONNECTED
for set/getState(Ex) when set to false and there was no prior input ever.
getBatteryInformation(option?: number | object): Promise<object>
Retrieves the battery type and charge status of a wireless controller.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- devType?: number (0)
Specifies which device associated with this controller should be queried.
0: GAMEPAD or 1: HEADSET
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object like a 📖 XINPUT_BATTERY_INFORMATION structure.
Example
await getBatteryInformation();
await getBatteryInformation(0);
await getBatteryInformation({dwUserIndex: 0});
//output
{
batteryType: 'BATTERY_TYPE_WIRED',
batteryLevel: 'BATTERY_LEVEL_FULL'
}
If you want raw data output
await getBatteryInformation({translate: false});
//output
{
batteryType: 1,
batteryLevel: 3
}
getCapabilities(option?: number | object): Promise<object>
Retrieves the capabilities and features of the specified controller.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- dwFlags?: number (1)
Input flags that identify the controller type.
If this value is 0, then the capabilities of all controllers connected to the system are returned.
Currently, only 1: XINPUT_FLAG_GAMEPAD is supported.
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object like a 📖 XINPUT_CAPABILITIES structure.
Example
await getCapabilities();
await getCapabilities(0);
await getCapabilities({gamepadIndex: 0});
//Output
{
type: 'XINPUT_DEVTYPE_GAMEPAD',
subType: 'XINPUT_DEVSUBTYPE_GAMEPAD',
flags: [ 'XINPUT_CAPS_VOICE_SUPPORTED', 'XINPUT_CAPS_PMD_SUPPORTED' ],
gamepad: {
wButtons: [
'XINPUT_GAMEPAD_DPAD_UP',
'XINPUT_GAMEPAD_DPAD_DOWN',
'XINPUT_GAMEPAD_DPAD_LEFT',
'XINPUT_GAMEPAD_DPAD_RIGHT',
'XINPUT_GAMEPAD_START',
'XINPUT_GAMEPAD_BACK',
'XINPUT_GAMEPAD_LEFT_THUMB',
'XINPUT_GAMEPAD_RIGHT_THUMB',
'XINPUT_GAMEPAD_LEFT_SHOULDER',
'XINPUT_GAMEPAD_RIGHT_SHOULDER',
'XINPUT_GAMEPAD_A',
'XINPUT_GAMEPAD_B',
'XINPUT_GAMEPAD_X',
'XINPUT_GAMEPAD_Y'
],
bLeftTrigger: 255,
bRightTrigger: 255,
sThumbLX: -64,
sThumbLY: -64,
sThumbRX: -64,
sThumbRY: -64
},
vibration: { wLeftMotorSpeed: 255, wRightMotorSpeed: 255 }
}
If you want raw data output
await getCapabilities({translate: false});
//output
{
type: 1,
subType: 1,
flags: 12,
gamepad: {
wButtons: 65535,
bLeftTrigger: 255,
bRightTrigger: 255,
sThumbLX: -64,
sThumbLY: -64,
sThumbRX: -64,
sThumbRY: -64
},
vibration: { wLeftMotorSpeed: 255, wRightMotorSpeed: 255 }
}
getKeystroke(option?: number | object): Promise<object>
Retrieves a gamepad input event.
To be honest, this isn't really useful since the chatpad feature wasn't implemented on Windows.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object like a 📖 XINPUT_KEYSTROKE structure.
Example
await getKeystroke();
await getKeystroke(0);
await getKeystroke({dwUserIndex: 0});
//Output
{
virtualKey: 'VK_PAD_A',
unicode: 0,
flags: [ 'XINPUT_KEYSTROKE_KEYDOWN' ],
userIndex: 0,
hidCode: 0
}
If you want raw data output
await getKeystroke({translate: false});
//output
{
virtualKey: 22528,
unicode: 0,
flags: 1,
userIndex: 0,
hidCode: 0
}
getState(option?: number | object): Promise<object>
Retrieves the current state of the specified controller.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object like a 📖 XINPUT_STATE structure.
Example
await getState();
await getState(0);
await getState({dwUserIndex: 0});
//Output
{
dwPacketNumber: 18165,
gamepad: {
wButtons: ['XINPUT_GAMEPAD_A'],
bLeftTrigger: 0,
bRightTrigger: 0,
sThumbLX: 128,
sThumbLY: 641,
sThumbRX: -1156,
sThumbRY: -129
}
}
If you want raw data output
await getState({translate: false});
//output
{
dwPacketNumber: 322850,
gamepad: {
wButtons: 4096,
bLeftTrigger: 0,
bRightTrigger: 0,
sThumbLX: 257,
sThumbLY: 767,
sThumbRX: 773,
sThumbRY: 1279
}
}
💡 Thumbsticks: as explained by Microsoft you should implement dead zone correctly.
This is done for you in getButtonsDown()
setState(lowFrequency: number, highFrequency: number, option ?: number | object): Promise<void>
Sends data to a connected controller. This function is used to activate the vibration function of a controller.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- usePercent?: boolean (true)
XInputSetState
valid values are in the range 0 to 65535.
Zero signifies no motor use; 65535 signifies 100 percent motor use.
lowFrequency
and highFrequency
are in % (0-100) for convenience when you set this to true.
💡 If option
is a number it will be used as dwUserIndex.
NB:
- You need to keep the event-loop alive otherwise the vibration will terminate with your program.
- You need to reset the state to 0 for both frequency before using setState again.
Both are done for you with rumble()
getStateEx(option?: number | object): Promise<object>
The same as XInputGetState
, adding the "Guide" button (0x0400).
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object like a 📖 XINPUT_STATE structure.
Example
await getStateEx();
await getStateEx(0);
await getStateEx({dwUserIndex: 0});
//Output
{
dwPacketNumber: 18165,
gamepad: {
wButtons: ['XINPUT_GAMEPAD_GUIDE'],
bLeftTrigger: 0,
bRightTrigger: 0,
sThumbLX: 128,
sThumbLY: 641,
sThumbRX: -1156,
sThumbRY: -129
}
}
If you want raw data output
await getStateEx({translate: false});
//output
{
dwPacketNumber: 322850,
gamepad: {
wButtons: 1024,
bLeftTrigger: 0,
bRightTrigger: 0,
sThumbLX: 257,
sThumbLY: 767,
sThumbRX: 773,
sThumbRY: 1279
}
}
waitForGuideButton(option?: number | object): Promise<void>
Wait until Guide button is pressed.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- dwFlags?: number (0)
Wait behavior:
0: Blocking 1: Async
It's not clear on how to get the async option to report.
💡 If option
is a number it will be used as dwUserIndex.
cancelGuideButtonWait(option?: number | object): Promise<void>
If XInputWaitForGuideButton
was activated in async mode, this will stop it.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
💡 If option
is a number it will be used as dwUserIndex.
powerOffController(option?: number | object): Promise<void>
Power off a controller.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
💡 If option
is a number it will be used as dwUserIndex.
getBaseBusInformation(option?: number | object): Promise<object>
ERROR_DEVICE_NOT_CONNECTED
, even if connected.
⚙️ options:
- dwBusIndex?: number (0)
Bus index. Can be a value from 0 to 16.
💡 If option
is a number it will be used as dwBusIndex?.
Returns an object like the following structure:
struct XINPUT_BASE_BUS_INFORMATION
{
WORD VendorId, //unknown
WORD ProductId, //unknown
WORD InputId, //unknown
WORD Field_6, //unknown
DWORD Field_8, //unknown
BYTE Field_C, //unknown
BYTE Field_D, //unknown
BYTE Field_E, //unknown
BYTE Field_F //unknown
}
getCapabilitiesEx(option?: number | object): Promise<object>
The same as XInputGetCapabilities
but with added properties such as vendorID and productID.
⚙️ options:
- dwUserIndex?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- translate?: boolean (true)
When a value is known it will be 'translated' to its string equivalent value otherwise its integer value.
If you want the raw data only set it to false.
💡 If option
is a number it will be used as dwUserIndex.
Returns an object similar to 📖 XINPUT_CAPABILITIES structure.
See below for details.
Example
await getCapabilitiesEx();
await getCapabilitiesEx(0);
await getCapabilitiesEx({gamepadIndex: 0});
//Output
{
capabilities: {
type: 'XINPUT_DEVTYPE_GAMEPAD',
dubType: 'XINPUT_DEVSUBTYPE_GAMEPAD',
flags: [ 'XINPUT_CAPS_VOICE_SUPPORTED', 'XINPUT_CAPS_PMD_SUPPORTED' ],
gamepad: {
wButtons: [
'XINPUT_GAMEPAD_DPAD_UP',
'XINPUT_GAMEPAD_DPAD_DOWN',
'XINPUT_GAMEPAD_DPAD_LEFT',
'XINPUT_GAMEPAD_DPAD_RIGHT',
'XINPUT_GAMEPAD_START',
'XINPUT_GAMEPAD_BACK',
'XINPUT_GAMEPAD_LEFT_THUMB',
'XINPUT_GAMEPAD_RIGHT_THUMB',
'XINPUT_GAMEPAD_LEFT_SHOULDER',
'XINPUT_GAMEPAD_RIGHT_SHOULDER',
'XINPUT_GAMEPAD_A',
'XINPUT_GAMEPAD_B',
'XINPUT_GAMEPAD_X',
'XINPUT_GAMEPAD_Y'
],
bLeftTrigger: 255,
bRightTrigger: 255,
sThumbLX: -64,
sThumbLY: -64,
sThumbRX: -64,
sThumbRY: -64
},
vibration: { wLeftMotorSpeed: 255, wRightMotorSpeed: 255 }
},
vendorId: 'Microsoft Corp.',
productId: 'Xbox360 Controller',
productVersion: 276,
}
If you want raw data output
await getCapabilitiesEx({translate: false});
//output
{
capabilities: {
type: 1,
dubType: 1,
flags: 12,
gamepad: {
wButtons: 62463,
bLeftTrigger: 255,
bRightTrigger: 255,
sThumbLX: -64,
sThumbLY: -64,
sThumbRX: -64,
sThumbRY: -64
},
vibration: {
wLeftMotorSpeed: 255,
wRightMotorSpeed: 255
}
},
vendorId: 1118,
productId: 654,
productVersion: 276,
}
Helper functions
The following are sugar/helper functions based upon the previous XInput functions.
isConnected(gamepad?: number): Promise<boolean>
Whether the specified controller is connected or not.
Returns true/false.
listConnected(): Promise<boolean[]>
Returns an array of connected status for all controller.
eg: [true,false,false,false] => Only 1st gamepad is connected
getButtonsDown(option?: object): Promise<object>
Normalize getState()/getStateEx()
information for convenience:
ThumbStick position, magnitude, direction (taking the deadzone into account).
Trigger state and force (taking threshold into account).
Which buttons are pressed if any.
⚙️ options:
- gamepad?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- deadzone?: number | number[] ( [7849,8689] )
Thumbstick deadzone(s):
Either an integer (both thumbstick with the same value) or an array of 2 integer: [left,right]
- directionThreshold?: number (0.2)
float [0.0,1.0] to handle cardinal direction.
Set it to 0
so direction[]
only reports "UP RIGHT", "UP LEFT", "DOWN LEFT", "DOWN RIGHT".
Otherwise "RIGHT", "LEFT", "UP", "DOWN" will be added to the above using threshold to
differentiate the 2 axes by using range of [-threshold,threshold].
💡 If you just want "RIGHT", "LEFT", "UP" and "DOWN" the easiest way is to set this to 0.8
with the default deadzone.
Alternatively play with this value and/or deadzone to decide on a thresold and ignore when direction[]
has a length of 2.
- triggerThreshold?: number (30)
Trigger activation threshold. Range [0,255].
=> Returns an object where:
- int packetNumber : dwPacketNumber; This value is increased every time the state of the controller has changed.
- []string buttons : list of currently pressed buttons
- trigger.left/right :
- boolean active : is the trigger pressed down ? (below triggerThreshold will not set active to true)
- int force : by how much ? [0,255]
- thumb.left/right :
- float x: normalized (deadzone) x axis [0.0,1.0]. 0 is centered. Negative values is left. Positive values is right.
- float y: normalized (deadzone) y axis [0.0,1.0]. 0 is centered. Negative values is down. Positive values is up.
- float magnitude: normalized (deadzone) magnitude [0.0,1.0] (by how far is the thumbstick from the center ? 1 is fully pushed).
- []string direction: Human readable direction of the thumbstick. eg: ["UP", "RIGHT"]. See directionThreshold above for details.
{
packetNumber: 132309,
buttons: [ 'XINPUT_GAMEPAD_A' ],
trigger: {
left: { active: true, force: 255 },
right: { active: false, force: 0 }
},
thumb: {
left: {
x: -0.6960457056589758,
y: 0.717997476063599,
magnitude: 1,
direction: [ 'UP', 'LEFT' ]
},
right: {
x: 0.039307955814283674,
y: 0.9992271436513833,
magnitude: 1,
direction: [ 'UP' ]
}
}
}
rumble(option?: object): Promise<void>
This function is used to activate the vibration function of a controller.
⚙️ options:
- gamepad?: number (0)
Index of the user's controller. Can be a value from 0 to 3.
- force?: number | number[] ([50,25])
Vibration force in % (0-100) to apply to the motors.
Either an integer (both motor with the same value) or an array of 2 integer: [left,right]
- duration?: number (2500)
Vibration duration in ms. Max: ~2500 ms.
- forceEnableGamepad?: boolean (false)
Use enable()
to force the activation of XInput gamepad before vibration.
- forceStateWhileRumble?: boolean (false)
Bruteforce -ly (spam) setState()
for the duration of the vibration. Use this when a 3rd party reset your state or whatever.
Identify device | VID/PID
XInput doesn't provide VID/PID by design.
Even if with XInputGetCapabilitiesEx
you can get the vendorID and productID, it will most likely be a Xbox Controller (real one or through XInput emulation).
Use identify()
(see below) to query WMI _Win32_PNPEntity
to scan for known gamepads.
It won't tell you which is connected to which XInput slot tho.
identify(option?: object): Promise<object[]>
List all known HID and USB connected devices by matching with entries in ./lib/util/HardwareID.js
⚙️ options:
- XInputOnly?: boolean (true)
Return only XInput gamepad.
=> Return an array of object where
- string name : device name
- string manufacturer : vendor name
- number vendorID : vendor id
- number productID : product id
- string[] interfaces : PNPentity interface(s) found; Available: HID and USB
- string[] guid: classguid(s) found
- boolean xinput: a XInput device or not
💡 object are unique by their vid/pid
Output example with a DS4(wireless) and ds4windows(XInput wrapper):
import { identify } from "xinput-ffi";
await identify();
//Output
[
{
name: 'DualShock 4 (v2)',
manufacturer: 'Sony Corp.',
vendorID: 1356,
productID: 2508,
xinput: false,
interfaces: [ 'USB', 'HID' ],
guid: [
'{36fc9e60-c465-11cf-8056-444553540000}',
'{745a17a0-74d3-11d0-b6fe-00a0c90f57da}',
'{4d36e96c-e325-11ce-bfc1-08002be10318}'
]
},
{
name: 'DualShock 4 USB Wireless Adaptor',
manufacturer: 'Sony Corp.',
vendorID: 1356,
productID: 2976,
xinput: false,
interfaces: [ 'USB', 'HID' ],
guid: [
'{745a17a0-74d3-11d0-b6fe-00a0c90f57da}',
'{36fc9e60-c465-11cf-8056-444553540000}',
'{4d36e96c-e325-11ce-bfc1-08002be10318}'
]
},
{
name: 'Xbox360 Controller',
manufacturer: 'Microsoft Corp.',
vendorID: 1118,
productID: 654,
xinput: true,
interfaces: [ 'USB', 'HID' ],
guid: [
'{745a17a0-74d3-11d0-b6fe-00a0c90f57da}',
'{d61ca365-5af4-4486-998b-9db4734c6ca3}'
]
}
]
High level implementation of XInput
This is a high level implementation of XInput to get the gamepad's input on the fly in a human readable way. This serves as an example to demonstrate how to use the XInput functions and helpers based around them. The purpose of this class is to drive a simple navigation menu system with a XInput compatible controller (real XInput or through XInput emulation).
This leverages the new Node.js timersPromises setInterval() to keep the event loop alive and do the gamepad polling.
XInputGamepad(option: object): Class
This class extends EventEmitter from node:events
Options
-
hz?: number (30)
This will determinate the polling rate. Usually 60hz (1000/60 = ~16ms) is used. If I'm not mistaken this is what the Chrome browser uses. But for our use case we don't need to poll that fast so it defaults to 30hz (~33ms). Increasing this value improves latency, but may cause a loss in performance due to more CPU time spent. The max accepted is 250hz (4ms).
-
multitap?: boolean (true)
Scan for all 4 XInput slots to find any Gamepad. Set to false to only poll XInput slot 0 and potentially reduce the number of FFI calls per gamepad tick (event loop).
-
joystickAsDPAD?: boolean (true)
Convert the left joystick analog axis to DPAD buttons. For our use case, driving a simple navigation menu, this is useful.
-
inputFeedback?: boolean (false)
Vibrate shortly and lightly on any button activation. This is just for fun and/or debug.
Events
input(buttons: string[])
List of activated buttons (human readable) of the first controller found.
A button is "activated" on press (button down) then release (button up).
💡 NB: Triggers axis are converted into non standard XInput button name : GAMEPAD_LEFT_TRIGGER
and GAMEPAD_RIGHT_TRIGGER
(on/off behavior).
XInput Button names:
"XINPUT_GAMEPAD_DPAD_UP",
"XINPUT_GAMEPAD_DPAD_DOWN",
"XINPUT_GAMEPAD_DPAD_LEFT",
"XINPUT_GAMEPAD_DPAD_RIGHT",
"XINPUT_GAMEPAD_START",
"XINPUT_GAMEPAD_BACK",
"XINPUT_GAMEPAD_LEFT_THUMB",
"XINPUT_GAMEPAD_RIGHT_THUMB",
"XINPUT_GAMEPAD_LEFT_SHOULDER",
"XINPUT_GAMEPAD_RIGHT_SHOULDER",
"XINPUT_GAMEPAD_GUIDE",
"XINPUT_GAMEPAD_A",
"XINPUT_GAMEPAD_B",
"XINPUT_GAMEPAD_X",
"XINPUT_GAMEPAD_Y"
💡 NB: XInput constants are available under the constants
namespace.
import { BUTTONS } from "xinput-ffi/constants";
//or
import { constants } from "xinput-ffi"
Example:
import { XInputGamepad } from "xinput-ffi";
const gamepad = new XInputGamepad({ hz: 60 });
gamepad.on("input", (buttons)=>{
setImmediate(() => {
console.log(buttons);
});
});
gamepad.poll();
Methods
poll()
Start the gamepad event loop. This will keep the Node.js event loop going.
❌ Will throw on unexpected error.
stop()
Stop the gamepad event loop.
NB: This method will remove every event listener.
pause()
This function is meant to be called when an application loses focus.
cf: XInputEnable
resume()
This function is meant to be called when an application gains focus.
cf: XInputEnable
vibrate(option: object): Promise<void>
Vibrate the first controller found. Shorthand to the helper fn rumble()
.
💡 Expose only force
and duration
options of rumble()
.
❌ Will throw on error other than ERROR_DEVICE_NOT_CONNECTED
.