Checkpoint
commit
7b1446982c
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
@ -0,0 +1,103 @@
|
|||||||
|
const WiiDevice = require("./wii-device.js");
|
||||||
|
const LoggerConfig = require("./logger-config.js");
|
||||||
|
const Config = require("./config");
|
||||||
|
const KeyboardSimulator = require("./keyboard-simulator.js");
|
||||||
|
const XboxPadSimulator = require("./xbox-pad-simulator.js");
|
||||||
|
|
||||||
|
class ClassicController extends WiiDevice {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
this.setupButtons({
|
||||||
|
A: 48,
|
||||||
|
B: 49,
|
||||||
|
Y: 52,
|
||||||
|
X: 51,
|
||||||
|
MINUS: 156,
|
||||||
|
HOME: 60,
|
||||||
|
PLUS: 151,
|
||||||
|
RIGHT: 106,
|
||||||
|
LEFT: 105,
|
||||||
|
UP: 103,
|
||||||
|
DOWN: 108,
|
||||||
|
ZR: 57,
|
||||||
|
R: 55,
|
||||||
|
ZL: 56,
|
||||||
|
L: 54,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configs = {
|
||||||
|
"Xbox360 controller": new Config(XboxPadSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "BTN_A",
|
||||||
|
B: "BTN_B",
|
||||||
|
Y: "BTN_Y",
|
||||||
|
X: "BTN_X",
|
||||||
|
UP: "BTN_DPAD_UP",
|
||||||
|
DOWN: "BTN_DPAD_DOWN",
|
||||||
|
LEFT: "BTN_DPAD_LEFT",
|
||||||
|
RIGHT: "BTN_DPAD_RIGHT",
|
||||||
|
PLUS: "BTN_START",
|
||||||
|
MINUS: "BTN_SELECT",
|
||||||
|
HOME: "BTN_MODE",
|
||||||
|
R: "BTN_TR",
|
||||||
|
L: "BTN_TL",
|
||||||
|
ZL: "BTN_TL2",
|
||||||
|
ZR: "BTN_TR2",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"Xbox360 controller - swap DPAD X/Y (starwhal)": new Config(
|
||||||
|
XboxPadSimulator,
|
||||||
|
{
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "BTN_A",
|
||||||
|
B: "BTN_B",
|
||||||
|
Y: "BTN_Y",
|
||||||
|
X: "BTN_X",
|
||||||
|
UP: "BTN_DPAD_LEFT",
|
||||||
|
DOWN: "BTN_DPAD_RIGHT",
|
||||||
|
LEFT: "BTN_DPAD_UP",
|
||||||
|
RIGHT: "BTN_DPAD_DOWN",
|
||||||
|
PLUS: "BTN_START",
|
||||||
|
MINUS: "BTN_SELECT",
|
||||||
|
HOME: "BTN_MODE",
|
||||||
|
R: "BTN_TR",
|
||||||
|
L: "BTN_TL",
|
||||||
|
ZL: "BTN_TL2",
|
||||||
|
ZR: "BTN_TR2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
logger: new LoggerConfig(),
|
||||||
|
"literal abxy (keyboard)": new Config(KeyboardSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "KEY_A",
|
||||||
|
B: "KEY_B",
|
||||||
|
Y: "KEY_Y",
|
||||||
|
X: "KEY_X",
|
||||||
|
MINUS: "KEY_MINUS",
|
||||||
|
HOME: "KEY_ESC",
|
||||||
|
PLUS: "KEY_EQUAL",
|
||||||
|
RIGHT: "KEY_RIGHT",
|
||||||
|
LEFT: "KEY_LEFT",
|
||||||
|
UP: "KEY_UP",
|
||||||
|
DOWN: "KEY_DOWN",
|
||||||
|
ZR: "KEY_Z",
|
||||||
|
R: "KEY_R",
|
||||||
|
ZL: "KEY_Q",
|
||||||
|
L: "KEY_L",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WiiDevice.registerType(
|
||||||
|
'"Nintendo Wii Remote Classic Controller"',
|
||||||
|
ClassicController
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = ClassicController;
|
@ -0,0 +1,35 @@
|
|||||||
|
class Config {
|
||||||
|
constructor(
|
||||||
|
simulator_class,
|
||||||
|
{ button_to_original_keycode, button_to_simulated_event_name }
|
||||||
|
) {
|
||||||
|
this.button_to_original_keycode = button_to_original_keycode;
|
||||||
|
this.button_to_simulated_event_name = button_to_simulated_event_name;
|
||||||
|
|
||||||
|
this.original_keycodes_to_button = {};
|
||||||
|
this.simulator_class = simulator_class;
|
||||||
|
|
||||||
|
for (let button in this.button_to_original_keycode) {
|
||||||
|
this.original_keycodes_to_button[
|
||||||
|
this.button_to_original_keycode[button]
|
||||||
|
] = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(wiimote) {
|
||||||
|
this.simulator = new this.simulator_class();
|
||||||
|
wiimote.on("input", event => {
|
||||||
|
if (event.key_code == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.simulator.simulateEvent({
|
||||||
|
key_name: this.button_to_simulated_event_name[
|
||||||
|
event.button_name
|
||||||
|
],
|
||||||
|
direction: event.direction,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Config;
|
@ -0,0 +1,41 @@
|
|||||||
|
const WiiDevices = require("./wii-devices.js");
|
||||||
|
const WiiDevice = require("./wii-device.js");
|
||||||
|
const sleep = require("./sleep.js");
|
||||||
|
const inquirer = require("inquirer");
|
||||||
|
|
||||||
|
async function wait_for_wii_devices() {
|
||||||
|
const wii_devices = WiiDevices.get_wii_devices();
|
||||||
|
if (wii_devices.length) {
|
||||||
|
return wii_devices;
|
||||||
|
}
|
||||||
|
console.log("Waiting for wii_devices...");
|
||||||
|
await sleep(1000);
|
||||||
|
return wii_devices.length ? wii_devices : await wait_for_wii_devices();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pick_wii_device() {
|
||||||
|
let wii_devices = WiiDevices.get_wii_devices();
|
||||||
|
if (wii_devices.length === 1) {
|
||||||
|
return wii_devices[0];
|
||||||
|
}
|
||||||
|
console.log("Press any button on the wii_device you want to configure");
|
||||||
|
return (await WiiDevices.wait_for_event()).wii_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await wait_for_wii_devices();
|
||||||
|
const wii_device = await pick_wii_device();
|
||||||
|
const { mode } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: "list",
|
||||||
|
name: "mode",
|
||||||
|
message: "which mode do you choose?",
|
||||||
|
choices: Object.keys(wii_device.configs),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
wii_device.setConfig(mode);
|
||||||
|
console.log(`Started ${wii_device.name} in '${mode}' mode`);
|
||||||
|
wii_device.setup_permanent_listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@ -0,0 +1,107 @@
|
|||||||
|
// from https://github.com/jehervy/node-virtual-gamepads/blob/master/lib/io.js
|
||||||
|
|
||||||
|
var io = {};
|
||||||
|
|
||||||
|
io["int"] = 4;
|
||||||
|
io["char"] = 1;
|
||||||
|
io["char*"] = 8;
|
||||||
|
|
||||||
|
io.UINPUT_IOCTL_BASE = "U".charCodeAt();
|
||||||
|
|
||||||
|
io._IOC_NONE = 0;
|
||||||
|
io._IOC_WRITE = 1;
|
||||||
|
io._IOC_READ = 2;
|
||||||
|
|
||||||
|
io._IOC_NRBITS = 8;
|
||||||
|
io._IOC_TYPEBITS = 8;
|
||||||
|
|
||||||
|
io._IOC_SIZEBITS = 14;
|
||||||
|
io._IOC_DIRBITS = 2;
|
||||||
|
|
||||||
|
io._IOC_NRMASK = (1 << io._IOC_NRBITS) - 1;
|
||||||
|
io._IOC_TYPEMASK = (1 << io._IOC_TYPEBITS) - 1;
|
||||||
|
io._IOC_SIZEMASK = (1 << io._IOC_SIZEBITS) - 1;
|
||||||
|
io._IOC_DIRMASK = (1 << io._IOC_DIRBITS) - 1;
|
||||||
|
|
||||||
|
io._IOC_NRSHIFT = 0;
|
||||||
|
io._IOC_TYPESHIFT = io._IOC_NRSHIFT + io._IOC_NRBITS;
|
||||||
|
io._IOC_SIZESHIFT = io._IOC_TYPESHIFT + io._IOC_TYPEBITS;
|
||||||
|
io._IOC_DIRSHIFT = io._IOC_SIZESHIFT + io._IOC_SIZEBITS;
|
||||||
|
|
||||||
|
io.sizeof = function(n) {
|
||||||
|
switch (typeof n) {
|
||||||
|
case "number":
|
||||||
|
return n;
|
||||||
|
case "string":
|
||||||
|
return n.length;
|
||||||
|
case "object":
|
||||||
|
return n.length ? n.length : 0;
|
||||||
|
case "undefined":
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOC = function(dir, type, nr, size) {
|
||||||
|
return (
|
||||||
|
(dir << io._IOC_DIRSHIFT) |
|
||||||
|
(type << io._IOC_TYPESHIFT) |
|
||||||
|
(nr << io._IOC_NRSHIFT) |
|
||||||
|
(size << io._IOC_SIZESHIFT)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOC_TYPECHECK = function(t) {
|
||||||
|
return io.sizeof(t);
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IO = function(type, nr) {
|
||||||
|
return io._IOC(io._IOC_NONE, type, nr, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOR = function(type, nr, size) {
|
||||||
|
return io._IOC(io._IOC_READ, type, nr, io._IOC_TYPECHECK(size));
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOW = function(type, nr, size) {
|
||||||
|
return io._IOC(io._IOC_WRITE, type, nr, io._IOC_TYPECHECK(size));
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOWR = function(type, nr, size) {
|
||||||
|
return io._IOC(
|
||||||
|
io._IOC_READ | io._IOC_WRITE,
|
||||||
|
type,
|
||||||
|
nr,
|
||||||
|
io._IOC_TYPECHECK(size)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOR_BAD = function(type, nr, size) {
|
||||||
|
return io._IOC(io._IOC_READ, type, nr, io.sizeof(size));
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOW_BAD = function(type, nr, size) {
|
||||||
|
return io._IOC(io._IOC_WRITE, type, nr, io.sizeof(size));
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOWR_BAD = function(type, nr, size) {
|
||||||
|
return io._IOC(io._IOC_READ | io._IOC_WRITE, type, nr, io.sizeof(size));
|
||||||
|
};
|
||||||
|
|
||||||
|
/* used to decode ioctl numbers.. */
|
||||||
|
io._IOC_DIR = function(nr) {
|
||||||
|
return (nr >> io._IOC_DIRSHIFT) & io._IOC_DIRMASK;
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOC_TYPE = function(nr) {
|
||||||
|
return (nr >> io._IOC_TYPESHIFT) & io._IOC_TYPEMASK;
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOC_NR = function(nr) {
|
||||||
|
return (nr >> io._IOC_NRSHIFT) & io._IOC_NRMASK;
|
||||||
|
};
|
||||||
|
|
||||||
|
io._IOC_SIZE = function(nr) {
|
||||||
|
return (nr >> io._IOC_SIZESHIFT) & io._IOC_SIZEMASK;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = io;
|
@ -0,0 +1,23 @@
|
|||||||
|
const Simulator = require("./simulator.js");
|
||||||
|
const uinput = require("./uinput");
|
||||||
|
|
||||||
|
class KeyboardSimulator extends Simulator {
|
||||||
|
constructor() {
|
||||||
|
const event_names = Object.keys(uinput).filter(event_name =>
|
||||||
|
event_name.startsWith("KEY_")
|
||||||
|
);
|
||||||
|
console.log(event_names);
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
name: "Custom device",
|
||||||
|
vendor: 0x1337,
|
||||||
|
product: 0x1010,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
event_names
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = KeyboardSimulator;
|
@ -0,0 +1,14 @@
|
|||||||
|
class LoggerConfig {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
start(device) {
|
||||||
|
device.on("input", event => {
|
||||||
|
if (event.key_code == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(event.key_code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LoggerConfig;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "wiimote-pads",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"blessed": "^0.1.81",
|
||||||
|
"inquirer": "^6.2.2",
|
||||||
|
"ioctl": "^2.0.1",
|
||||||
|
"linux-device": "^2.0.15",
|
||||||
|
"node-hid": "^0.7.7",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-blessed": "^0.5.0",
|
||||||
|
"restruct": "^0.1.3",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const ioctl = require("ioctl");
|
||||||
|
const uinput = require("./uinput");
|
||||||
|
|
||||||
|
const { O_NONBLOCK, O_WRONLY } = fs.constants;
|
||||||
|
|
||||||
|
class Simulator {
|
||||||
|
constructor({ name, vendor, product, version }, event_names) {
|
||||||
|
this.fd = fs.openSync("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||||
|
|
||||||
|
const bustype = 0x3; // USB
|
||||||
|
|
||||||
|
const buff = Buffer.alloc(1116);
|
||||||
|
buff.fill(0);
|
||||||
|
buff.write(name, 0, 80);
|
||||||
|
buff.writeUInt16LE(bustype, 80);
|
||||||
|
buff.writeUInt16LE(vendor, 82);
|
||||||
|
buff.writeUInt16LE(product, 84);
|
||||||
|
buff.writeUInt16LE(version, 86);
|
||||||
|
|
||||||
|
fs.writeSync(this.fd, buff, 0, buff.length, null);
|
||||||
|
ioctl(this.fd, uinput.UI_SET_EVBIT, uinput.EV_KEY);
|
||||||
|
for (let event_name of event_names) {
|
||||||
|
console.log("registering event", event_name, uinput[event_name]);
|
||||||
|
ioctl(this.fd, uinput.UI_SET_KEYBIT, uinput[event_name]);
|
||||||
|
}
|
||||||
|
ioctl(this.fd, uinput.UI_DEV_CREATE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateEvent({ key_name, direction }) {
|
||||||
|
//usage: simulateEvent({key_name: "KEY_A", direction: "down" | "up"});
|
||||||
|
var ev = Buffer.alloc(24);
|
||||||
|
ev.fill(0);
|
||||||
|
|
||||||
|
var tv_sec = Math.round(Date.now() / 1000),
|
||||||
|
tv_usec = Math.round((Date.now() % 1000) * 1000),
|
||||||
|
type = 0x01, // EV_KEY,
|
||||||
|
code = uinput[key_name],
|
||||||
|
value = direction == "down" ? 1 : 0;
|
||||||
|
|
||||||
|
ev.writeInt32LE(tv_sec, 0);
|
||||||
|
ev.writeInt32LE(tv_usec, 8);
|
||||||
|
ev.writeInt16LE(type, 16);
|
||||||
|
ev.writeInt16LE(code, 18);
|
||||||
|
ev.writeInt32LE(value, 20);
|
||||||
|
|
||||||
|
var ev_sync_report = Buffer.alloc(24);
|
||||||
|
ev_sync_report.fill(0);
|
||||||
|
|
||||||
|
ev_sync_report.writeInt32LE(tv_sec, 0);
|
||||||
|
ev_sync_report.writeInt32LE(tv_usec, 8);
|
||||||
|
|
||||||
|
fs.writeSync(this.fd, ev, 0, ev.length, null);
|
||||||
|
fs.writeSync(this.fd, ev_sync_report, 0, ev_sync_report.length, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Simulator;
|
@ -0,0 +1,3 @@
|
|||||||
|
const util = require("util");
|
||||||
|
|
||||||
|
module.exports = util.promisify((time, cb) => setTimeout(cb, time));
|
@ -0,0 +1,185 @@
|
|||||||
|
// from http://art.vexillium.org/posts/nodejs-gamepad-driver/content/uinput.js
|
||||||
|
// http://art.vexillium.org/posts/nodejs-gamepad-driver/
|
||||||
|
|
||||||
|
// types of events from: /usr/include/linux/input-event-codes.h
|
||||||
|
|
||||||
|
var restruct = require("restruct"),
|
||||||
|
io = require("./io");
|
||||||
|
|
||||||
|
var uinput = {};
|
||||||
|
|
||||||
|
uinput.UI_SET_EVBIT = io._IOW(io.UINPUT_IOCTL_BASE, 100, io["int"]);
|
||||||
|
uinput.UI_SET_KEYBIT = io._IOW(io.UINPUT_IOCTL_BASE, 101, io["int"]);
|
||||||
|
uinput.UI_SET_RELBIT = io._IOW(io.UINPUT_IOCTL_BASE, 102, io["int"]);
|
||||||
|
uinput.UI_SET_ABSBIT = io._IOW(io.UINPUT_IOCTL_BASE, 103, io["int"]);
|
||||||
|
//https://github.com/torvalds/linux/blob/1fc7f56db7a7c467e46a5d2e2a009d2f337e0338/include/uapi/linux/input.h#L182
|
||||||
|
uinput.EVIOCGRAB = io._IOW("E".charCodeAt(0), 0x90, io["int"]);
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/1fc7f56db7a7c467e46a5d2e2a009d2f337e0338/include/uapi/linux/input.h#L178
|
||||||
|
|
||||||
|
uinput.EVIOCSFF = io._IOW("E".charCodeAt(0), 0x80, io["ff_effect"]);
|
||||||
|
|
||||||
|
uinput.UI_DEV_CREATE = io._IO(io.UINPUT_IOCTL_BASE, 1);
|
||||||
|
uinput.UI_DEV_DESTROY = io._IO(io.UINPUT_IOCTL_BASE, 2);
|
||||||
|
|
||||||
|
uinput.EV_SYN = 0x00;
|
||||||
|
uinput.EV_KEY = 0x01;
|
||||||
|
uinput.EV_REL = 0x02;
|
||||||
|
uinput.EV_ABS = 0x03;
|
||||||
|
|
||||||
|
uinput.BTN_MOUSE = 0x110;
|
||||||
|
uinput.BTN_LEFT = 0x110;
|
||||||
|
uinput.BTN_RIGHT = 0x111;
|
||||||
|
uinput.BTN_MIDDLE = 0x112;
|
||||||
|
|
||||||
|
uinput.BTN_MOUSE = 0x110;
|
||||||
|
uinput.BTN_LEFT = 0x110;
|
||||||
|
uinput.BTN_RIGHT = 0x111;
|
||||||
|
uinput.BTN_MIDDLE = 0x112;
|
||||||
|
uinput.BTN_SIDE = 0x113;
|
||||||
|
uinput.BTN_EXTRA = 0x114;
|
||||||
|
uinput.BTN_FORWARD = 0x115;
|
||||||
|
uinput.BTN_BACK = 0x116;
|
||||||
|
uinput.BTN_TASK = 0x117;
|
||||||
|
uinput.BTN_JOYSTICK = 0x120;
|
||||||
|
uinput.BTN_TRIGGER = 0x120;
|
||||||
|
uinput.BTN_THUMB = 0x121;
|
||||||
|
uinput.BTN_THUMB2 = 0x122;
|
||||||
|
uinput.BTN_TOP = 0x123;
|
||||||
|
uinput.BTN_TOP2 = 0x124;
|
||||||
|
uinput.BTN_PINKIE = 0x125;
|
||||||
|
uinput.BTN_BASE = 0x126;
|
||||||
|
uinput.BTN_BASE2 = 0x127;
|
||||||
|
uinput.BTN_BASE3 = 0x128;
|
||||||
|
uinput.BTN_BASE4 = 0x129;
|
||||||
|
uinput.BTN_BASE5 = 0x12a;
|
||||||
|
uinput.BTN_BASE6 = 0x12b;
|
||||||
|
uinput.BTN_DEAD = 0x12f;
|
||||||
|
uinput.BTN_GAMEPAD = 0x130;
|
||||||
|
uinput.BTN_A = 0x130;
|
||||||
|
uinput.BTN_B = 0x131;
|
||||||
|
uinput.BTN_C = 0x132;
|
||||||
|
uinput.BTN_X = 0x133;
|
||||||
|
uinput.BTN_Y = 0x134;
|
||||||
|
uinput.BTN_Z = 0x135;
|
||||||
|
uinput.BTN_TL = 0x136;
|
||||||
|
uinput.BTN_TR = 0x137;
|
||||||
|
uinput.BTN_TL2 = 0x138;
|
||||||
|
uinput.BTN_TR2 = 0x139;
|
||||||
|
uinput.BTN_SELECT = 0x13a;
|
||||||
|
uinput.BTN_START = 0x13b;
|
||||||
|
uinput.BTN_MODE = 0x13c;
|
||||||
|
uinput.BTN_THUMBL = 0x13d;
|
||||||
|
uinput.BTN_THUMBR = 0x13e;
|
||||||
|
uinput.REL_X = 0x00;
|
||||||
|
uinput.REL_Y = 0x01;
|
||||||
|
uinput.REL_WHEEL = 0x08;
|
||||||
|
uinput.ABS_X = 0x00;
|
||||||
|
uinput.ABS_Y = 0x01;
|
||||||
|
uinput.ABS_Z = 0x02;
|
||||||
|
|
||||||
|
uinput.ABS_RX = 0x03;
|
||||||
|
uinput.ABS_RY = 0x04;
|
||||||
|
uinput.ABS_RZ = 0x05;
|
||||||
|
uinput.ABS_HAT0X = 0x10;
|
||||||
|
uinput.ABS_HAT0Y = 0x11;
|
||||||
|
uinput.ABS_MISC = 0x28;
|
||||||
|
|
||||||
|
uinput.ID_BUS = 0;
|
||||||
|
uinput.BUS_USB = 0x3;
|
||||||
|
|
||||||
|
uinput.KEY_RESERVED = 0;
|
||||||
|
uinput.KEY_ESC = 1;
|
||||||
|
uinput.KEY_1 = 2;
|
||||||
|
uinput.KEY_2 = 3;
|
||||||
|
uinput.KEY_3 = 4;
|
||||||
|
uinput.KEY_4 = 5;
|
||||||
|
uinput.KEY_5 = 6;
|
||||||
|
uinput.KEY_6 = 7;
|
||||||
|
uinput.KEY_7 = 8;
|
||||||
|
uinput.KEY_8 = 9;
|
||||||
|
uinput.KEY_9 = 10;
|
||||||
|
uinput.KEY_0 = 11;
|
||||||
|
uinput.KEY_MINUS = 12;
|
||||||
|
uinput.KEY_EQUAL = 13;
|
||||||
|
uinput.KEY_BACKSPACE = 14;
|
||||||
|
uinput.KEY_TAB = 15;
|
||||||
|
uinput.KEY_Q = 16;
|
||||||
|
uinput.KEY_W = 17;
|
||||||
|
uinput.KEY_E = 18;
|
||||||
|
uinput.KEY_R = 19;
|
||||||
|
uinput.KEY_T = 20;
|
||||||
|
uinput.KEY_Y = 21;
|
||||||
|
uinput.KEY_U = 22;
|
||||||
|
uinput.KEY_I = 23;
|
||||||
|
uinput.KEY_O = 24;
|
||||||
|
uinput.KEY_P = 25;
|
||||||
|
uinput.KEY_LEFTBRACE = 26;
|
||||||
|
uinput.KEY_RIGHTBRACE = 27;
|
||||||
|
uinput.KEY_ENTER = 28;
|
||||||
|
uinput.KEY_LEFTCTRL = 29;
|
||||||
|
uinput.KEY_A = 30;
|
||||||
|
uinput.KEY_S = 31;
|
||||||
|
uinput.KEY_D = 32;
|
||||||
|
uinput.KEY_F = 33;
|
||||||
|
uinput.KEY_G = 34;
|
||||||
|
uinput.KEY_H = 35;
|
||||||
|
uinput.KEY_J = 36;
|
||||||
|
uinput.KEY_K = 37;
|
||||||
|
uinput.KEY_L = 38;
|
||||||
|
uinput.KEY_SEMICOLON = 39;
|
||||||
|
uinput.KEY_APOSTROPHE = 40;
|
||||||
|
uinput.KEY_GRAVE = 41;
|
||||||
|
uinput.KEY_LEFTSHIFT = 42;
|
||||||
|
uinput.KEY_BACKSLASH = 43;
|
||||||
|
uinput.KEY_Z = 44;
|
||||||
|
uinput.KEY_X = 45;
|
||||||
|
uinput.KEY_C = 46;
|
||||||
|
uinput.KEY_V = 47;
|
||||||
|
uinput.KEY_B = 48;
|
||||||
|
uinput.KEY_N = 49;
|
||||||
|
uinput.KEY_M = 50;
|
||||||
|
uinput.KEY_COMMA = 51;
|
||||||
|
uinput.KEY_DOT = 52;
|
||||||
|
uinput.KEY_SLASH = 53;
|
||||||
|
uinput.KEY_RIGHTSHIFT = 54;
|
||||||
|
uinput.KEY_KPASTERISK = 55;
|
||||||
|
uinput.KEY_LEFTALT = 56;
|
||||||
|
uinput.KEY_SPACE = 57;
|
||||||
|
uinput.KEY_CAPSLOCK = 58;
|
||||||
|
uinput.KEY_F1 = 59;
|
||||||
|
uinput.KEY_F2 = 60;
|
||||||
|
uinput.KEY_F3 = 61;
|
||||||
|
uinput.KEY_F4 = 62;
|
||||||
|
uinput.KEY_F5 = 63;
|
||||||
|
uinput.KEY_F6 = 64;
|
||||||
|
uinput.KEY_F7 = 65;
|
||||||
|
uinput.KEY_F8 = 66;
|
||||||
|
uinput.KEY_F9 = 67;
|
||||||
|
uinput.KEY_F10 = 68;
|
||||||
|
|
||||||
|
uinput.BTN_DPAD_UP = 0x220;
|
||||||
|
uinput.BTN_DPAD_DOWN = 0x221;
|
||||||
|
uinput.BTN_DPAD_LEFT = 0x222;
|
||||||
|
uinput.BTN_DPAD_RIGHT = 0x223;
|
||||||
|
|
||||||
|
uinput.UINPUT_MAX_NAME_SIZE = 80;
|
||||||
|
|
||||||
|
uinput.input_id = restruct
|
||||||
|
.int16lu("bustype")
|
||||||
|
.int16lu("vendor")
|
||||||
|
.int16lu("product")
|
||||||
|
.int16lu("version")
|
||||||
|
.pad(1012);
|
||||||
|
|
||||||
|
uinput.uinput_user_dev = restruct
|
||||||
|
.string("name", uinput.UINPUT_MAX_NAME_SIZE)
|
||||||
|
.struct("id", uinput.input_id)
|
||||||
|
.int32ls("absmax")
|
||||||
|
.int32ls("absmin")
|
||||||
|
.int32ls("absfuzz")
|
||||||
|
.int32ls("absflat");
|
||||||
|
|
||||||
|
for (var i in uinput) {
|
||||||
|
module.exports[i] = uinput[i];
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const ioctl = require("ioctl");
|
||||||
|
const uinput = require("./uinput");
|
||||||
|
const uuid = require("uuid/v1");
|
||||||
|
const EventEmitter = require("events");
|
||||||
|
const { spawn } = require("child_process");
|
||||||
|
const KeyboardSimulator = require("./keyboard-simulator.js");
|
||||||
|
|
||||||
|
class WiiDeviceEvent {
|
||||||
|
constructor(buffer, wii_device) {
|
||||||
|
this.key_code = buffer[18];
|
||||||
|
this.button_name = wii_device.keycodes[this.key_code];
|
||||||
|
this.type = buffer[20] ? "press" : "release";
|
||||||
|
this.direction = buffer[20] ? "down" : "up";
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.wii_device = wii_device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WIIMOTE_EVENT_BYTESIZE = 24;
|
||||||
|
|
||||||
|
const types = {};
|
||||||
|
|
||||||
|
class WiiDevice extends EventEmitter {
|
||||||
|
constructor({ name, handlers }) {
|
||||||
|
super();
|
||||||
|
this.handlers = handlers;
|
||||||
|
this.name = name;
|
||||||
|
this.handler_file = this.get_handler_file(handlers);
|
||||||
|
this.id = uuid();
|
||||||
|
this.setup_temporary_listener();
|
||||||
|
this.buttons = {};
|
||||||
|
this.keycodes = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static registerType(name, constructor) {
|
||||||
|
types[name] = constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromDesc(desc) {
|
||||||
|
return new types[desc.name](desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPossibleTypes() {
|
||||||
|
return Object.keys(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupButtons(button_name_to_original_evcode) {
|
||||||
|
this.buttons = button_name_to_original_evcode;
|
||||||
|
this.keycodes = {};
|
||||||
|
|
||||||
|
for (let button in this.buttons) {
|
||||||
|
this.keycodes[this.buttons[button]] = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_temporary_listener() {
|
||||||
|
this.cat = spawn("cat", [this.handler_file]);
|
||||||
|
this.stream = this.cat.stdout;
|
||||||
|
this.pressed_buttons_count = 0;
|
||||||
|
this.stream.on("data", buf => {
|
||||||
|
while (buf.length) {
|
||||||
|
let slice = buf.slice(0, WIIMOTE_EVENT_BYTESIZE);
|
||||||
|
this.emit("input", new WiiDeviceEvent(slice, this));
|
||||||
|
buf = buf.slice(WIIMOTE_EVENT_BYTESIZE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_permanent_listener() {
|
||||||
|
if (this.cat) {
|
||||||
|
this.cat.kill();
|
||||||
|
}
|
||||||
|
const handler_file_fd = fs.openSync(this.handler_file);
|
||||||
|
try {
|
||||||
|
// grabbing device: https://unix.stackexchange.com/questions/492909/access-grab-state-of-evdev-device
|
||||||
|
ioctl(handler_file_fd, uinput.EVIOCGRAB, 0x1);
|
||||||
|
console.log("grabbed device");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Couldn't grab device.");
|
||||||
|
console.error(e);
|
||||||
|
process.exit(13);
|
||||||
|
}
|
||||||
|
const stream = fs.createReadStream("any", { fd: handler_file_fd });
|
||||||
|
stream.on("data", buf => {
|
||||||
|
while (buf.length) {
|
||||||
|
let slice = buf.slice(0, WIIMOTE_EVENT_BYTESIZE);
|
||||||
|
this.emit("input", new WiiDeviceEvent(slice, this));
|
||||||
|
buf = buf.slice(WIIMOTE_EVENT_BYTESIZE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
has_handler(handler_name) {
|
||||||
|
return this.handlers.includes(handler_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_handler_file(handlers) {
|
||||||
|
return `/dev/input/${
|
||||||
|
handlers.filter(handler => handler.startsWith("event"))[0]
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config_key) {
|
||||||
|
this.configs[config_key].start(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
// console.log("Closing the stream for", this.id);
|
||||||
|
this.removeAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WiiDevice;
|
||||||
|
|
||||||
|
require("./wiimote.js");
|
||||||
|
require("./classic-controller.js");
|
@ -0,0 +1,124 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const WiiDevice = require("./wii-device.js");
|
||||||
|
const EventEmitter = require("events");
|
||||||
|
const { spawn } = require("child_process");
|
||||||
|
|
||||||
|
class WiiDevices extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.wii_devices = [];
|
||||||
|
this.refresh_wii_devices();
|
||||||
|
this.inotify = spawn("inotifywait", [
|
||||||
|
"-m",
|
||||||
|
"-r",
|
||||||
|
"-e",
|
||||||
|
"CREATE",
|
||||||
|
"-e",
|
||||||
|
"DELETE",
|
||||||
|
"/dev/input",
|
||||||
|
]).stdout;
|
||||||
|
this.inotify.on("data", () => this.refresh_wii_devices());
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseDevice(string) {
|
||||||
|
const obj = {};
|
||||||
|
string
|
||||||
|
.split("\n")
|
||||||
|
.map(line => line.split(": "))
|
||||||
|
.filter(line_elements => ["N", "H"].includes(line_elements[0]))
|
||||||
|
.map(line_elements => line_elements[1])
|
||||||
|
.map(prop_desc => prop_desc.split("="))
|
||||||
|
.forEach(
|
||||||
|
prop_desc_elements =>
|
||||||
|
(obj[prop_desc_elements[0].toLowerCase()] =
|
||||||
|
prop_desc_elements[1])
|
||||||
|
);
|
||||||
|
if (obj.handlers) {
|
||||||
|
obj.handlers = obj.handlers.trim().split(" ");
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_wii_device_new(description) {
|
||||||
|
return !description.handlers
|
||||||
|
.filter(h => h != "kbd")
|
||||||
|
.some(handler =>
|
||||||
|
this.wii_devices.some(wii_device =>
|
||||||
|
wii_device.has_handler(handler)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_wii_device_if_new(description) {
|
||||||
|
if (this.is_wii_device_new(description)) {
|
||||||
|
const wii_device = WiiDevice.fromDesc(description);
|
||||||
|
this.wii_devices.push(wii_device);
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_wii_device_if_not_in_list(devices_list, wii_device) {
|
||||||
|
if (
|
||||||
|
!devices_list
|
||||||
|
.map(e => e.handlers)
|
||||||
|
.reduce((a, b) => a.concat(b), [])
|
||||||
|
.filter(handler => handler != "kbd")
|
||||||
|
.filter(handler => wii_device.has_handler(handler)).length
|
||||||
|
) {
|
||||||
|
wii_device.close();
|
||||||
|
this.wii_devices.splice(
|
||||||
|
this.wii_devices
|
||||||
|
.map(wii_device => wii_device.id)
|
||||||
|
.indexOf(wii_device.id),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_wii_devices() {
|
||||||
|
try {
|
||||||
|
const devices = fs
|
||||||
|
.readFileSync("/proc/bus/input/devices", "utf-8")
|
||||||
|
.trim();
|
||||||
|
const entries = devices
|
||||||
|
.split("\n\n")
|
||||||
|
.map(WiiDevices.parseDevice)
|
||||||
|
.filter(d => WiiDevice.getPossibleTypes().includes(d.name));
|
||||||
|
entries.forEach(wii_device_description =>
|
||||||
|
this.add_wii_device_if_new(wii_device_description)
|
||||||
|
);
|
||||||
|
this.wii_devices.forEach(wii_device =>
|
||||||
|
this.remove_wii_device_if_not_in_list(entries, wii_device)
|
||||||
|
);
|
||||||
|
return this.wii_devices;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return this.refresh_wii_devices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_wii_devices() {
|
||||||
|
return this.wii_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
quit() {
|
||||||
|
this.wii_devices.forEach(wii_device => wii_device.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
async wait_for_event() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const listener = event => {
|
||||||
|
this.wii_devices.forEach(wii_device =>
|
||||||
|
wii_device.removeListener("input", listener)
|
||||||
|
);
|
||||||
|
resolve(event);
|
||||||
|
};
|
||||||
|
this.wii_devices.forEach(wii_device =>
|
||||||
|
wii_device.once("input", listener)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new WiiDevices();
|
@ -0,0 +1,133 @@
|
|||||||
|
const WiiDevice = require("./wii-device.js");
|
||||||
|
const Config = require("./config.js");
|
||||||
|
const KeyboardSimulator = require("./keyboard-simulator.js");
|
||||||
|
const XboxPadSimulator = require("./xbox-pad-simulator.js");
|
||||||
|
const LoggerConfig = require("./logger-config.js");
|
||||||
|
|
||||||
|
class Wiimote extends WiiDevice {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
this.setupButtons({
|
||||||
|
A: 48,
|
||||||
|
B: 49,
|
||||||
|
MINUS: 156,
|
||||||
|
HOME: 60,
|
||||||
|
PLUS: 151,
|
||||||
|
ONE: 1,
|
||||||
|
TWO: 2,
|
||||||
|
RIGHT: 106,
|
||||||
|
LEFT: 105,
|
||||||
|
UP: 103,
|
||||||
|
DOWN: 108,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configs = {
|
||||||
|
"Xbox360 controller": new Config(XboxPadSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
TWO: "BTN_A",
|
||||||
|
ONE: "BTN_B",
|
||||||
|
B: "BTN_Y",
|
||||||
|
A: "BTN_X",
|
||||||
|
UP: "BTN_DPAD_LEFT",
|
||||||
|
DOWN: "BTN_DPAD_RIGHT",
|
||||||
|
LEFT: "BTN_DPAD_DOWN",
|
||||||
|
RIGHT: "BTN_DPAD_UP",
|
||||||
|
PLUS: "BTN_START",
|
||||||
|
MINUS: "BTN_SELECT",
|
||||||
|
HOME: "BTN_MODE",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"Xbox360 controller - Towerfall": new Config(XboxPadSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
TWO: "BTN_B",
|
||||||
|
ONE: "BTN_Y",
|
||||||
|
B: "BTN_TR",
|
||||||
|
A: "BTN_X",
|
||||||
|
UP: "BTN_DPAD_LEFT",
|
||||||
|
DOWN: "BTN_DPAD_RIGHT",
|
||||||
|
LEFT: "BTN_DPAD_DOWN",
|
||||||
|
RIGHT: "BTN_DPAD_UP",
|
||||||
|
PLUS: "BTN_START",
|
||||||
|
MINUS: "BTN_A",
|
||||||
|
HOME: "BTN_MODE",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"Xbox360 controller - swap dpad X/Y (Starwhal)": new Config(
|
||||||
|
XboxPadSimulator,
|
||||||
|
{
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
TWO: "BTN_A",
|
||||||
|
ONE: "BTN_B",
|
||||||
|
B: "BTN_Y",
|
||||||
|
A: "BTN_X",
|
||||||
|
UP: "BTN_DPAD_UP",
|
||||||
|
DOWN: "BTN_DPAD_DOWN",
|
||||||
|
LEFT: "BTN_DPAD_RIGHT",
|
||||||
|
RIGHT: "BTN_DPAD_LEFT",
|
||||||
|
PLUS: "BTN_START",
|
||||||
|
MINUS: "BTN_SELECT",
|
||||||
|
HOME: "BTN_MODE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
wsad: new Config(KeyboardSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "KEY_Q",
|
||||||
|
B: "KEY_E",
|
||||||
|
UP: "KEY_A",
|
||||||
|
DOWN: "KEY_D",
|
||||||
|
LEFT: "KEY_S",
|
||||||
|
RIGHT: "KEY_W",
|
||||||
|
HOME: "KEY_ESC",
|
||||||
|
MINUS: "KEY_Z",
|
||||||
|
PLUS: "KEY_X",
|
||||||
|
ONE: "KEY_1",
|
||||||
|
TWO: "KEY_2",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"Keyboard - 0123..": new Config(KeyboardSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "KEY_0",
|
||||||
|
B: "KEY_1",
|
||||||
|
UP: "KEY_2",
|
||||||
|
DOWN: "KEY_3",
|
||||||
|
LEFT: "KEY_4",
|
||||||
|
RIGHT: "KEY_5",
|
||||||
|
TWO: "KEY_6",
|
||||||
|
MINUS: "KEY_7",
|
||||||
|
PLUS: "KEY_8",
|
||||||
|
ONE: "KEY_9",
|
||||||
|
HOME: "KEY_ESC",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"Keyboard - QWERTY": new Config(KeyboardSimulator, {
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_original_keycode: this.buttons,
|
||||||
|
button_to_simulated_event_name: {
|
||||||
|
A: "KEY_Q",
|
||||||
|
B: "KEY_W",
|
||||||
|
UP: "KEY_E",
|
||||||
|
DOWN: "KEY_R",
|
||||||
|
LEFT: "KEY_T",
|
||||||
|
RIGHT: "KEY_Y",
|
||||||
|
TWO: "KEY_U",
|
||||||
|
MINUS: "KEY_I",
|
||||||
|
PLUS: "KEY_O",
|
||||||
|
ONE: "KEY_P",
|
||||||
|
HOME: "KEY_ESC",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
logger: new LoggerConfig(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WiiDevice.registerType('"Nintendo Wii Remote"', Wiimote);
|
||||||
|
|
||||||
|
module.exports = Wiimote;
|
@ -0,0 +1,34 @@
|
|||||||
|
const Simulator = require("./simulator.js");
|
||||||
|
|
||||||
|
class XboxControllerSimulator extends Simulator {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
// https://www.the-sz.com/products/usbid/index.php?v=0x045E
|
||||||
|
{
|
||||||
|
name: "Microsoft X-Box 360 pad",
|
||||||
|
vendor: 0x045e,
|
||||||
|
product: 0x028e,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"X",
|
||||||
|
"Y",
|
||||||
|
"TR",
|
||||||
|
"TL",
|
||||||
|
"TR2",
|
||||||
|
"TL2",
|
||||||
|
"SELECT",
|
||||||
|
"START",
|
||||||
|
"MODE",
|
||||||
|
"DPAD_UP",
|
||||||
|
"DPAD_DOWN",
|
||||||
|
"DPAD_LEFT",
|
||||||
|
"DPAD_RIGHT",
|
||||||
|
].map(btn_name => `BTN_${btn_name}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = XboxControllerSimulator;
|
Loading…
Reference in New Issue