dualsense-ts
Version:
A natural interface for your DualSense controller, with Typescript
290 lines • 15.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HIDProvider = exports.DefaultDualsenseHIDState = exports.mapGyroAccel = exports.mapTrigger = exports.mapAxis = void 0;
__exportStar(require("../id"), exports);
/** Maps a HID input of 0...n to -1...1 */
function mapAxis(value, max = 255) {
return (2 / max) * Math.max(0, Math.min(max, value)) - 1;
}
exports.mapAxis = mapAxis;
/** Maps a HID input of 0...255 to 0...1 */
function mapTrigger(value) {
return (1 / 255) * Math.max(0, Math.min(255, value));
}
exports.mapTrigger = mapTrigger;
/**
* Maps a HID input for either gyroscope or acceleration.
* Adapted from https://github.com/nondebug/dualsense
*/
function mapGyroAccel(v0, v1) {
let v = (v1 << 8) | v0;
if (v > 0x7fff)
v -= 0x10000;
return mapAxis(v + 0x7fff, 0xffff);
}
exports.mapGyroAccel = mapGyroAccel;
/** Default values for all inputs */
exports.DefaultDualsenseHIDState = {
["LX" /* InputId.LeftAnalogX */]: 0,
["LY" /* InputId.LeftAnalogY */]: 0,
["RX" /* InputId.RightAnalogX */]: 0,
["RY" /* InputId.RightAnalogY */]: 0,
["L2" /* InputId.LeftTrigger */]: 0,
["R2" /* InputId.RightTrigger */]: 0,
["Triangle" /* InputId.Triangle */]: false,
["Circle" /* InputId.Circle */]: false,
["Cross" /* InputId.Cross */]: false,
["Square" /* InputId.Square */]: false,
["Dpad" /* InputId.Dpad */]: 0,
["Up" /* InputId.Up */]: false,
["Down" /* InputId.Down */]: false,
["Left" /* InputId.Left */]: false,
["Right" /* InputId.Right */]: false,
["R3" /* InputId.RightAnalogButton */]: false,
["L3" /* InputId.LeftAnalogButton */]: false,
["Options" /* InputId.Options */]: false,
["Create" /* InputId.Create */]: false,
["R2Button" /* InputId.RightTriggerButton */]: false,
["L2Button" /* InputId.LeftTriggerButton */]: false,
["R1" /* InputId.RightBumper */]: false,
["L1" /* InputId.LeftBumper */]: false,
["Playstation" /* InputId.Playstation */]: false,
["TouchButton" /* InputId.TouchButton */]: false,
["Mute" /* InputId.Mute */]: false,
["Status" /* InputId.Status */]: false,
["TouchX0" /* InputId.TouchX0 */]: 0,
["TouchY0" /* InputId.TouchY0 */]: 0,
["TouchContact0" /* InputId.TouchContact0 */]: false,
["TouchId0" /* InputId.TouchId0 */]: 0,
["TouchX1" /* InputId.TouchX1 */]: 0,
["TouchY1" /* InputId.TouchY1 */]: 0,
["TouchContact1" /* InputId.TouchContact1 */]: false,
["TouchId1" /* InputId.TouchId1 */]: 0,
["GyroX" /* InputId.GyroX */]: 0,
["GyroY" /* InputId.GyroY */]: 0,
["GyroZ" /* InputId.GyroZ */]: 0,
["AccelX" /* InputId.AccelX */]: 0,
["AccelY" /* InputId.AccelY */]: 0,
["AccelZ" /* InputId.AccelZ */]: 0,
};
/** Supports a connection to a physical or virtual Dualsense device */
class HIDProvider {
constructor() {
/** Callback to use for new input events */
this.onData = () => { };
/** Callback to use for Error events */
this.onError = () => { };
}
/**
* Sselects the correct method for reading the report.
* @param buffer HID report buffer
*/
processReport(buffer) {
const reportId = buffer.readUint8(0);
switch (reportId) {
case 0x01:
return this.wireless
? this.processBluetoothInputReport01(buffer)
: this.processUsbInputReport01(buffer);
case 0x31:
return this.processBluetoothInputReport31(buffer);
default:
this.onError(new Error(`Cannot process report, unexpected report id: ${reportId}`));
this.disconnect();
return { ...exports.DefaultDualsenseHIDState };
}
}
/**
* Reset the HIDProvider state when the device is disconnected
*/
reset() {
this.device = undefined;
this.wireless = undefined;
this.buffer = undefined;
this.limited = undefined;
this.onData(exports.DefaultDualsenseHIDState);
}
/**
* Process a bluetooth input report of type 01.
* @param buffer the report
*/
processBluetoothInputReport01(buffer) {
this.limited = true;
const buttonsAndDpad = buffer.readUint8(5);
const buttons = buttonsAndDpad >> 4;
const dpad = buttonsAndDpad & 0b1111;
const miscButtons = buffer.readUint8(6);
const lastButtons = buffer.readUint8(7);
return {
...exports.DefaultDualsenseHIDState,
["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(1)),
["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(2)),
["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(3)),
["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(4)),
["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(8)),
["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(9)),
["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0,
["Circle" /* InputId.Circle */]: (buttons & 4) > 0,
["Cross" /* InputId.Cross */]: (buttons & 2) > 0,
["Square" /* InputId.Square */]: (buttons & 1) > 0,
["Dpad" /* InputId.Dpad */]: dpad,
["Up" /* InputId.Up */]: dpad < 2 || dpad === 7,
["Down" /* InputId.Down */]: dpad > 2 && dpad < 6,
["Left" /* InputId.Left */]: dpad > 4 && dpad < 8,
["Right" /* InputId.Right */]: dpad > 0 && dpad < 4,
["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0,
["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0,
["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0,
["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0,
["Create" /* InputId.Create */]: (miscButtons & 16) > 0,
["Options" /* InputId.Options */]: (miscButtons & 32) > 0,
["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0,
["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0,
["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0,
["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0,
// See https://github.com/nondebug/dualsense/blob/main/dualsense-explorer.html#L338
//
// "By default, bluetooth-connected DualSense only sends input report 0x01 which omits motion and touchpad data.
// Reading feature report 0x05 causes it to start sending input report 0x31.
//
// Note: The Gamepad API will do this for us if it enumerates the gamepad.
// Other applications like Steam may have also done this already."
};
}
/** Process bluetooth input report of type 31 */
processBluetoothInputReport31(buffer) {
this.limited = false;
const buttonsAndDpad = buffer.readUint8(9);
const buttons = buttonsAndDpad >> 4;
const dpad = buttonsAndDpad & 0b1111;
const miscButtons = buffer.readUint8(10);
const lastButtons = buffer.readUint8(11);
return {
["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(2)),
["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(3)),
["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(4)),
["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(5)),
["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(6)),
["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(7)),
["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0,
["Circle" /* InputId.Circle */]: (buttons & 4) > 0,
["Cross" /* InputId.Cross */]: (buttons & 2) > 0,
["Square" /* InputId.Square */]: (buttons & 1) > 0,
["Dpad" /* InputId.Dpad */]: dpad,
["Up" /* InputId.Up */]: dpad < 2 || dpad === 7,
["Down" /* InputId.Down */]: dpad > 2 && dpad < 6,
["Left" /* InputId.Left */]: dpad > 4 && dpad < 8,
["Right" /* InputId.Right */]: dpad > 0 && dpad < 4,
["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0,
["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0,
["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0,
["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0,
["Create" /* InputId.Create */]: (miscButtons & 16) > 0,
["Options" /* InputId.Options */]: (miscButtons & 32) > 0,
["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0,
["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0,
["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0,
["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0,
["Mute" /* InputId.Mute */]: (lastButtons & 4) > 0,
["GyroX" /* InputId.GyroX */]: mapGyroAccel(buffer.readUint8(17), buffer.readUint8(18)),
["GyroY" /* InputId.GyroY */]: mapGyroAccel(buffer.readUint8(19), buffer.readUint8(20)),
["GyroZ" /* InputId.GyroZ */]: mapGyroAccel(buffer.readUint8(21), buffer.readUint8(22)),
["AccelX" /* InputId.AccelX */]: mapGyroAccel(buffer.readUint8(23), buffer.readUint8(24)),
["AccelY" /* InputId.AccelY */]: mapGyroAccel(buffer.readUint8(25), buffer.readUint8(26)),
["AccelZ" /* InputId.AccelZ */]: mapGyroAccel(buffer.readUint8(27), buffer.readUint8(28)),
["TouchId0" /* InputId.TouchId0 */]: buffer.readUint8(34) & 0x7f,
["TouchContact0" /* InputId.TouchContact0 */]: (buffer.readUint8(34) & 0x80) === 0,
["TouchX0" /* InputId.TouchX0 */]: mapAxis((buffer.readUint16LE(35) << 20) >> 20, 1920),
["TouchY0" /* InputId.TouchY0 */]: mapAxis(buffer.readUint16LE(36) >> 4, 1080),
["TouchId1" /* InputId.TouchId1 */]: buffer.readUint8(38) & 0x7f,
["TouchContact1" /* InputId.TouchContact1 */]: (buffer.readUint8(38) & 0x80) === 0,
["TouchX1" /* InputId.TouchX1 */]: mapAxis((buffer.readUint16LE(39) << 20) >> 20, 1920),
["TouchY1" /* InputId.TouchY1 */]: mapAxis(buffer.readUint16LE(40) >> 4, 1080),
["Status" /* InputId.Status */]: (buffer.readUint8(55) & 4) > 0,
};
}
/**
* Process a USB input report of type 01.
* @param buffer the report
*/
processUsbInputReport01(buffer) {
this.limited = false;
const buttonsAndDpad = buffer.readUint8(8);
const buttons = buttonsAndDpad >> 4;
const dpad = buttonsAndDpad & 0b1111;
const miscButtons = buffer.readUint8(9);
const lastButtons = buffer.readUint8(10);
return {
["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(1)),
["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(2)),
["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(3)),
["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(4)),
["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(5)),
["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(6)),
["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0,
["Circle" /* InputId.Circle */]: (buttons & 4) > 0,
["Cross" /* InputId.Cross */]: (buttons & 2) > 0,
["Square" /* InputId.Square */]: (buttons & 1) > 0,
["Dpad" /* InputId.Dpad */]: dpad,
["Up" /* InputId.Up */]: dpad < 2 || dpad === 7,
["Down" /* InputId.Down */]: dpad > 2 && dpad < 6,
["Left" /* InputId.Left */]: dpad > 4 && dpad < 8,
["Right" /* InputId.Right */]: dpad > 0 && dpad < 4,
["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0,
["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0,
["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0,
["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0,
["Create" /* InputId.Create */]: (miscButtons & 16) > 0,
["Options" /* InputId.Options */]: (miscButtons & 32) > 0,
["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0,
["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0,
["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0,
["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0,
["Mute" /* InputId.Mute */]: (lastButtons & 4) > 0,
// The other 5 bits are unused
// 5 reserved bytes
["GyroX" /* InputId.GyroX */]: mapGyroAccel(buffer.readUint8(16), buffer.readUint8(17)),
["GyroY" /* InputId.GyroY */]: mapGyroAccel(buffer.readUint8(18), buffer.readUint8(19)),
["GyroZ" /* InputId.GyroZ */]: mapGyroAccel(buffer.readUint8(20), buffer.readUint8(21)),
["AccelX" /* InputId.AccelX */]: mapGyroAccel(buffer.readUint8(22), buffer.readUint8(23)),
["AccelY" /* InputId.AccelY */]: mapGyroAccel(buffer.readUint8(24), buffer.readUint8(25)),
["AccelZ" /* InputId.AccelZ */]: mapGyroAccel(buffer.readUint8(26), buffer.readUint8(27)),
// 4 bytes for sensor timestamp (32LE)
// 1 reserved byte
["TouchId0" /* InputId.TouchId0 */]: buffer.readUint8(33) & 0x7f,
["TouchContact0" /* InputId.TouchContact0 */]: (buffer.readUint8(33) & 0x80) === 0,
["TouchX0" /* InputId.TouchX0 */]: mapAxis((buffer.readUint16LE(34) << 20) >> 20, 1920),
["TouchY0" /* InputId.TouchY0 */]: mapAxis(buffer.readUint16LE(35) >> 4, 1080),
["TouchId1" /* InputId.TouchId1 */]: buffer.readUint8(37) & 0x7f,
["TouchContact1" /* InputId.TouchContact1 */]: (buffer.readUint8(37) & 0x80) === 0,
["TouchX1" /* InputId.TouchX1 */]: mapAxis((buffer.readUint16LE(38) << 20) >> 20, 1920),
["TouchY1" /* InputId.TouchY1 */]: mapAxis(buffer.readUint16LE(39) >> 4, 1080),
// 12 reserved bytes
["Status" /* InputId.Status */]: (buffer.readUint8(54) & 4) > 0,
};
}
}
exports.HIDProvider = HIDProvider;
/** HID vendorId for a Dualsense controller */
HIDProvider.vendorId = 1356;
/** HID productId for a Dualsense controller */
HIDProvider.productId = 3302;
/** HID usagePage for a Dualsense controller */
HIDProvider.usagePage = 0x0001;
/** HID usage for a Dualsense controller */
HIDProvider.usage = 0x0005;
//# sourceMappingURL=hid_provider.js.map