UNPKG

xinput-ffi

Version:

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

1,075 lines (806 loc) 29.1 kB
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).<br/> It aims to implement and expose XInput functions as close as possible to the document.<br/> 🔍 "Hidden" XInput functions such as `XInputGetCapabilitiesEx()` are exposed as well. Examples ======== <details> <summary>Vibration via helper function</summary> ```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]}); ``` </details> <details> <summary>XInput function</summary> ```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 } } */ ``` </details> <details> <summary>"Hidden" XInput function</summary> ```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 } } */ ``` </details> <details> <summary>Miscellaneous</summary> ```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}' ] } ] */ ``` </details> ### Electron <details> <summary>Simple XInput menu navigation</summary> 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); } }); ``` </details> Installation ============ ``` npm install xinput-ffi ``` API === ⚠️ This module is only available as an ECMAScript module (ESM) starting with version 2.0.0.<br /> 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<br/> 📖 [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<void>` Enable/Disable all XInput gamepads.<br/> 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<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.<br/> 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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> 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<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. <br/> If this value is 0, then the capabilities of all controllers connected to the system are returned.<br/> 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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> 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<object>` Retrieves a gamepad input event.<br/> To be honest, this isn't really useful since the chatpad feature wasn't implemented on Windows.<br/> ⚠️ 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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> 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<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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> 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).<br/> 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<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.<br /> Zero signifies no motor use; 65535 signifies 100 percent motor use.<br /> `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.<br/> NB: - You need to keep the event-loop alive otherwise the vibration will terminate with your program.<br /> - You need to reset the state to 0 for both frequency before using setState again.<br /> 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<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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> 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<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:<br/> 0: Blocking 1: Async<br/> It's not clear on how to get the async option to report. 💡 If `option` is a number it will be used as dwUserIndex.<br/> #### `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.<br/> #### `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.<br/> #### `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?.<br/> 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<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.<br/> If you want the raw data only set it to false. 💡 If `option` is a number it will be used as dwUserIndex.<br/> Returns an object similar to 📖 [XINPUT_CAPABILITIES](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_capabilities) structure.<br/> 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, } ``` </details> ### 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.<br /> Returns true/false. #### `listConnected(): Promise<boolean[]>` Returns an array of connected status for all controller.<br /> eg: [true,false,false,false] => Only 1st gamepad is connected #### `getButtonsDown(option?: object): Promise<object>` Normalize `getState()/getStateEx()` information for convenience:<br/> ThumbStick position, magnitude, direction (taking the deadzone into account).<br/> Trigger state and force (taking threshold into account).<br/> Which buttons are pressed if any.<br/> ⚙️ 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):<br/> Either an integer (both thumbstick with the same value) or an array of 2 integer: [left,right]<br/> - directionThreshold?: number (0.2) float [0.0,1.0] to handle cardinal direction.<br/> Set it to `0` so `direction[]` only reports "UP RIGHT", "UP LEFT", "DOWN LEFT", "DOWN RIGHT".<br/> Otherwise "RIGHT", "LEFT", "UP", "DOWN" will be added to the above using threshold to <br/> 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.<br/> 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<void>` This function is used to activate the vibration function of a controller.<br /> ⚙️ 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.<br/> 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.<br/> ⚠️ Usage of this option is not recommended use only when needed. ### Identify device | VID/PID XInput doesn't provide VID/PID **by design**.<br /> 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).<br /> Use `identify()` (see below) to query `WMI _Win32_PNPEntity` to scan for known gamepads.<br /> 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): ```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.<br /> 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_). <details><summary>XInput Button names:</summary> ``` "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" ``` </details> 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<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`.