xinput-ffi
v4.0.1
Published
Access native XInput functions as well as some helpers based around them.
Downloads
178
Maintainers
Readme
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
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]});
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 }
}
*/
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
}
}
*/
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
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
⚠️ This module is only available as an ECMAScript module (ESM) starting with version 2.0.0. 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({dwUserIndex: 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. ⚠️ NB: If no new keys have been pressed, this will throw with ERROR_EMPTY.
⚙️ 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>
⚠️ Not working on all gamepads. It can refuse and return 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({dwUserIndex: 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.
⚠️ Usage of this option is not recommended use only when needed.
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[]>
⚠️ Requires PowerShell.
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_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
.