xinput-ffi
TypeScript icon, indicating that this package has built-in type declarations

4.0.0 • Public • Published

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

⚠️ 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:

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

📖 Microsoft documentation

"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.

📖 XInputEnable

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
}

📖 XInputGetBatteryInformation

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 }
}

📖 XInputGetCapabilities

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 
}

📖 XInputGetKeystroke

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()

📖 XInputGetState

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()

📖 XInputSetState

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({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.
⚠️ 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 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.

Package Sidebar

Install

npm i xinput-ffi

Weekly Downloads

9

Version

4.0.0

License

MIT

Unpacked Size

85 kB

Total Files

27

Last publish

Collaborators

  • xan105