Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/xan105/node-xinput-ffi

Access native XInput functions as well as some helpers based around them.
https://github.com/xan105/node-xinput-ffi

ffi gamepad nodejs pid rumble vibrate vid windows xinput

Last synced: about 1 month ago
JSON representation

Access native XInput functions as well as some helpers based around them.

Awesome Lists containing this project

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

Vibration via helper function

```js
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

```js
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

```js
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

```js
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

```js

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)

```js
contextBridge.exposeInMainWorld("ipcRenderer", {
onGamepadInput: (callback) => ipcRenderer.on("onGamepadInput", callback)
});
```

+ renderer

```js
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](#const-constants--object)
- [XInput function](#xinput-function)
- [Helper functions](#helper-functions)
- [Identify device | VID/PID](#identify-device--vidpid)
- [High level implementation of XInput](#high-level-implementation-of-xinput)

### `const constants = object`

XInput controller constants for convenience.

```js
import { constants } from "xinput-ffi";
console.log(constants.XUSER_MAX_COUNT); //4
```

💡 Also available under its own namespace.

```js
import { XUSER_MAX_COUNT } from "xinput-ffi/constants";
console.log(XUSER_MAX_COUNT); //4
```

### XInput function

📖 [Microsoft documentation](https://docs.microsoft.com/en-us/windows/win32/xinput/functions)

- ✔️ [XInputEnable](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputenable)
- ❌ [XInputGetAudioDeviceIds]() _> deprecated: doesn't work on modern Windows system._
- ✔️ [XInputGetBatteryInformation](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetbatteryinformation)
- ✔️ [XInputGetCapabilities](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetcapabilities)
- ❌ [XInputGetDSoundAudioDeviceGuids]() _> deprecated: doesn't work on modern Windows system._
- ✔️ [XInputGetKeystroke](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetkeystroke)
- ✔️ [XInputGetState](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetstate)
- ✔️ [XInputSetState](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputsetstate)

"Hidden" and undocumented functions

📖 [Reverse Engineer's log](https://reverseengineerlog.blogspot.com/2016/06/xinputs-hidden-functions.html)

- ✔️ 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`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputenable)

#### `getBatteryInformation(option?: number | object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_battery_information) structure.

Example
```js
await getBatteryInformation();
await getBatteryInformation(0);
await getBatteryInformation({dwUserIndex: 0});
//output
{
batteryType: 'BATTERY_TYPE_WIRED',
batteryLevel: 'BATTERY_LEVEL_FULL'
}
```

If you want raw data output
```js
await getBatteryInformation({translate: false});
//output
{
batteryType: 1,
batteryLevel: 3
}
```

📖 [XInputGetBatteryInformation](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetbatteryinformation)

#### `getCapabilities(option?: number | object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_capabilities) structure.

Example
```js
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
```js
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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetcapabilities)

#### `getKeystroke(option?: number | object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_keystroke) structure.

Example
```js
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
```js
await getKeystroke({translate: false});
//output
{
virtualKey: 22528,
unicode: 0,
flags: 1,
userIndex: 0,
hidCode: 0
}
```

📖 [XInputGetKeystroke](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetkeystroke)

#### `getState(option?: number | object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_state) structure.

Example
```js
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
```js
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](https://docs.microsoft.com/en-us/windows/win32/xinput/getting-started-with-xinput#dead-zone).

This is done for you in [getButtonsDown()](https://github.com/xan105/node-xinput-ffi#getbuttonsdown-option-object-object)

📖 [XInputGetState](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetstate)

#### `setState(lowFrequency: number, highFrequency: number, option ?: number | object): Promise`

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()](https://github.com/xan105/node-xinput-ffi#rumble-option-object-void)

📖 [XInputSetState](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputsetstate)

#### `getStateEx(option?: number | object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_state) structure.

Example
```js
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
```js
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`

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`

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`

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`

⚠️ 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:
```c++
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`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_capabilities) structure.

See below for details.

Example
```js
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
```js
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`

Whether the specified controller is connected or not.

Returns true/false.

#### `listConnected(): Promise`

Returns an array of connected status for all controller.

eg: [true,false,false,false] => Only 1st gamepad is connected

#### `getButtonsDown(option?: object): Promise`

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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad#members)
- 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.

```js
{
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`

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`

⚠️ 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):
```js
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.

```js
import { BUTTONS } from "xinput-ffi/constants";
//or
import { constants } from "xinput-ffi"
```

Example:

```js
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](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputenable)_

##### `resume()`

This function is meant to be called when an application gains focus.

_cf: [XInputEnable](https://docs.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputenable)_

#### `vibrate(option: object): Promise`

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