xinput-ffi
Version:
Access native XInput functions as well as some helpers based around them.
406 lines (322 loc) • 13.5 kB
JavaScript
/*
Copyright (c) Anthony Beaumont
This source code is licensed under the MIT License
found in the LICENSE file in the root directory of this source tree.
*/
import { Failure, errorLookup } from "@xan105/error";
import { isIntegerWithinRange, isNumber } from "@xan105/is";
import {
shouldObj,
shouldBoolean,
shouldIntegerWithinRange,
} from "@xan105/is/assert";
import { asBoolean } from "@xan105/is/opt";
import { symbols } from "./symbols.js";
import * as CONTROLLER from "./constants.js";
import { dlopenEx } from "../util/ffi.js";
import { bitwise } from "../util/bitwise.js";
import { hardwareID } from "../util/HardwareID.js";
const lib = dlopenEx("xinput", ["1_4", "1_3", "9_1_0"], symbols, {
abi: "stdcall",
nonblocking: true
});
async function enable(enable){
if (!lib.XInputEnable)
throw new Failure(...errorLookup(120), "win32");
shouldBoolean(enable);
await lib.XInputEnable(enable ? 1 : 0);
}
async function getBatteryInformation(option = {}){
if (!lib.XInputGetBatteryInformation)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
devType: Object.keys(CONTROLLER.BATTERY_DEVTYPE).includes(option.devType) ? option.devType : 0,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const BatteryInformation = {};
const code = await lib.XInputGetBatteryInformation(options.dwUserIndex, options.devType, BatteryInformation);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
batteryType: options.translate ? CONTROLLER.BATTERY_TYPE[BatteryInformation.BatteryType] ??
BatteryInformation.BatteryType : BatteryInformation.BatteryType,
batteryLevel: options.translate ? CONTROLLER.BATTERY_LEVEL[BatteryInformation.BatteryLevel] ??
BatteryInformation.BatteryLevel : BatteryInformation.BatteryLevel
};
return result;
}
async function getCapabilities(option = {}){
if (!lib.XInputGetCapabilities)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
//Any values other than 0 or 1 (XINPUT_FLAG_GAMEPAD) are illegal and will result in an error break
dwFlags: [0, CONTROLLER.FLAG_GAMEPAD].includes(option.dwFlags) ? option.dwFlags : CONTROLLER.FLAG_GAMEPAD,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const Capabilities = {};
const code = await lib.XInputGetCapabilities(options.dwUserIndex, options.dwFlags, Capabilities);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
type: options.translate ? CONTROLLER.DEVTYPE[Capabilities.Type] ??
Capabilities.Type : Capabilities.Type,
subType: options.translate ? CONTROLLER.DEVSUBTYPE[Capabilities.SubType] ??
Capabilities.SubType : Capabilities.SubType,
flags: options.translate ? bitwise(Capabilities.Flags, CONTROLLER.FEATURES) : Capabilities.Flags,
gamepad: {
wButtons: options.translate ? bitwise(Capabilities.Gamepad.wButtons, CONTROLLER.BUTTONS) : Capabilities.Gamepad.wButtons,
bLeftTrigger: Capabilities.Gamepad.bLeftTrigger,
bRightTrigger: Capabilities.Gamepad.bRightTrigger,
sThumbLX: Capabilities.Gamepad.sThumbLX,
sThumbLY: Capabilities.Gamepad.sThumbLY,
sThumbRX: Capabilities.Gamepad.sThumbRX,
sThumbRY: Capabilities.Gamepad.sThumbRY
},
vibration: {
wLeftMotorSpeed: Capabilities.Vibration.wLeftMotorSpeed,
wRightMotorSpeed: Capabilities.Vibration.wRightMotorSpeed,
}
};
return result;
}
async function getKeystroke(option = {}){
if (!lib.XInputGetKeystroke)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
if (
!( isIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1) ||
options.dwUserIndex === CONTROLLER.XUSER_INDEX_ANY )
){
throw new Failure("Expecting an integer between the specified range or equals to 255 !", {
code: 1,
info: {
type: typeof options.dwUserIndex,
value: options.dwUserIndex,
range: [0, CONTROLLER.XUSER_MAX_COUNT - 1],
alternate: CONTROLLER.XUSER_INDEX_ANY
}
});
}
const Keystroke = {};
const code = await lib.XInputGetKeystroke(options.dwUserIndex, 0, Keystroke);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
virtualKey: options.translate ? CONTROLLER.VIRTUALKEY[Keystroke.VirtualKey] ??
Keystroke.VirtualKey : Keystroke.VirtualKey,
unicode: Keystroke.Unicode,
flags: options.translate ? bitwise(Keystroke.Flags, CONTROLLER.VK_STATE) : Keystroke.Flags,
userIndex: Keystroke.UserIndex,
hidCode: Keystroke.HidCode,
};
return result;
}
async function getState(option = {}){
if (!lib.XInputGetState)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const State = {};
const code = await lib.XInputGetState(options.dwUserIndex, State);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
dwPacketNumber: State.dwPacketNumber,
gamepad: {
wButtons: options.translate ? bitwise(State.Gamepad.wButtons, CONTROLLER.BUTTONS): State.Gamepad.wButtons,
bLeftTrigger: State.Gamepad.bLeftTrigger,
bRightTrigger: State.Gamepad.bRightTrigger,
sThumbLX: State.Gamepad.sThumbLX,
sThumbLY: State.Gamepad.sThumbLY,
sThumbRX: State.Gamepad.sThumbRX,
sThumbRY: State.Gamepad.sThumbRY
}
};
return result;
}
async function setState(lowFrequency, highFrequency, option = {}){
if (!lib.XInputSetState)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
usePercent: option.usePercent ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const forceFeedBack = function(i){
shouldIntegerWithinRange(i, 0, 100);
return Math.floor((CONTROLLER.MOTOR_SPEED / 100) * i);
};
const Vibration = {
wLeftMotorSpeed: options.usePercent ? forceFeedBack(lowFrequency) : lowFrequency,
wRightMotorSpeed: options.usePercent ? forceFeedBack(highFrequency) : highFrequency
};
//Explicit error
shouldIntegerWithinRange(Vibration.wLeftMotorSpeed, 0, CONTROLLER.MOTOR_SPEED);
shouldIntegerWithinRange(Vibration.wRightMotorSpeed, 0, CONTROLLER.MOTOR_SPEED);
const code = await lib.XInputSetState(options.dwUserIndex, Vibration);
if (code !== 0) throw new Failure(...errorLookup(code));
}
async function getStateEx(option = {}){
if (!lib.XInputGetStateEx)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const State = {};
const code = await lib.XInputGetStateEx(options.dwUserIndex, State);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
dwPacketNumber: State.dwPacketNumber,
gamepad: {
wButtons: options.translate ? bitwise(State.Gamepad.wButtons, CONTROLLER.BUTTONS): State.Gamepad.wButtons,
bLeftTrigger: State.Gamepad.bLeftTrigger,
bRightTrigger: State.Gamepad.bRightTrigger,
sThumbLX: State.Gamepad.sThumbLX,
sThumbLY: State.Gamepad.sThumbLY,
sThumbRX: State.Gamepad.sThumbRX,
sThumbRY: State.Gamepad.sThumbRY
}
};
return result;
}
async function waitForGuideButton(option = {}){
if (!lib.XInputWaitForGuideButton)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
dwFlags: [0,1].includes(option.dwFlags) ? option.dwFlags : 0 //async(1) blocking(0)
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const code = await lib.XInputWaitForGuideButton(options.dwUserIndex, options.dwFlags, {});
if (code !== 0) throw new Failure(...errorLookup(code));
}
async function cancelGuideButtonWait(option = {}){
if (!lib.XInputCancelGuideButtonWait)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const code = await lib.XInputCancelGuideButtonWait(options.dwUserIndex);
if (code !== 0) throw new Failure(...errorLookup(code));
}
async function powerOffController(option = {}){
if (!lib.XInputPowerOffController)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const code = await lib.XInputPowerOffController(options.dwUserIndex);
if (code !== 0) throw new Failure(...errorLookup(code));
}
async function getBaseBusInformation(option = {}){
if (!lib.XInputGetBaseBusInformation)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwBusIndex: option };
shouldObj(option);
const options = {
dwBusIndex: option.dwBusIndex ?? 0
};
//Explicit error
shouldIntegerWithinRange(options.dwBusIndex, 0, 16);
const Information = {};
const code = await lib.XInputGetBaseBusInformation(options.dwBusIndex, Information);
if (code !== 0) throw new Failure(...errorLookup(code));
return Information;
}
async function getCapabilitiesEx(option = {}){
if (!lib.XInputGetCapabilitiesEx)
throw new Failure(...errorLookup(120), "win32");
if (isNumber(option)) option = { dwUserIndex: option };
shouldObj(option);
const options = {
dwUserIndex: option.dwUserIndex ?? 0,
translate: asBoolean(option.translate) ?? true
};
//Explicit error
shouldIntegerWithinRange(options.dwUserIndex, 0, CONTROLLER.XUSER_MAX_COUNT - 1);
const dwFlags = 0; //unknown
const Capabilities = {};
const code = await lib.XInputGetCapabilitiesEx(1 /*unknown*/, options.dwUserIndex, dwFlags, Capabilities);
if (code !== 0) throw new Failure(...errorLookup(code));
const result = {
capabilities: {
type: options.translate ? CONTROLLER.DEVTYPE[Capabilities.Capabilities.Type] ??
Capabilities.Capabilities.Type : Capabilities.Capabilities.Type,
dubType: options.translate ? CONTROLLER.DEVSUBTYPE[Capabilities.Capabilities.SubType] ??
Capabilities.Capabilities.SubType : Capabilities.Capabilities.SubType,
flags: options.translate ? bitwise(Capabilities.Capabilities.Flags, CONTROLLER.FEATURES) : Capabilities.Capabilities.Flags,
gamepad: {
wButtons: options.translate ? bitwise(Capabilities.Capabilities.Gamepad.wButtons, CONTROLLER.BUTTONS) : Capabilities.Capabilities.Gamepad.wButtons,
bLeftTrigger: Capabilities.Capabilities.Gamepad.bLeftTrigger,
bRightTrigger: Capabilities.Capabilities.Gamepad.bRightTrigger,
sThumbLX: Capabilities.Capabilities.Gamepad.sThumbLX,
sThumbLY: Capabilities.Capabilities.Gamepad.sThumbLY,
sThumbRX: Capabilities.Capabilities.Gamepad.sThumbRX,
sThumbRY: Capabilities.Capabilities.Gamepad.sThumbRY,
},
vibration: {
wLeftMotorSpeed: Capabilities.Capabilities.Vibration.wLeftMotorSpeed,
wRightMotorSpeed: Capabilities.Capabilities.Vibration.wRightMotorSpeed,
}
},
vendorId: options.translate ? hardwareID[Capabilities.VendorId]?.name ??
Capabilities.VendorId : Capabilities.VendorId,
productId: options.translate ? hardwareID[Capabilities.VendorId]?.controller?.[Capabilities.ProductId] ??
Capabilities.ProductId : Capabilities.ProductId,
productVersion: Capabilities.ProductVersion,
unk1: Capabilities.unk1,
unk2: Capabilities.unk2
};
return result;
}
export {
enable,
getBatteryInformation,
getCapabilities,
getKeystroke,
getState,
setState,
getStateEx,
waitForGuideButton,
cancelGuideButtonWait,
powerOffController,
getBaseBusInformation,
getCapabilitiesEx
};