├── include ├── vjoyinterface.h ├── gen-versioninfo.h ├── stdafx.h └── public.h ├── native ├── x64 │ ├── vJoyInterface.dll │ └── vJoyInterface.lib └── ia32 │ ├── vJoyInterface.dll │ └── vJoyInterface.lib ├── index.d.ts ├── index.js ├── lib ├── ffb_manager.d.ts ├── vjoy_axis.d.ts ├── vjoy_button.d.ts ├── vjoy_disc_pov.d.ts ├── vjoy_cont_pov.d.ts ├── vjoy.d.ts ├── vjoy_button.js ├── vjoy_axis.js ├── ffb_manager.js ├── vjoy_disc_pov.js ├── vjoy_cont_pov.js ├── vjoy.js ├── vjoy_device.d.ts └── vjoy_device.js ├── src ├── utils.cpp ├── utils.h ├── device.h ├── feeding.h ├── general.h ├── ffb.h ├── device.cpp ├── vjoy.cpp ├── feeding.cpp ├── general.cpp └── ffb.cpp ├── examples ├── display_info.ts ├── display_info.js ├── feeder.js ├── feeder.ts └── force_feedback.js ├── binding.gyp ├── LICENSE.txt ├── package.json ├── .gitignore └── README.md /include/vjoyinterface.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangxx/node-vjoy/HEAD/include/vjoyinterface.h -------------------------------------------------------------------------------- /native/x64/vJoyInterface.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangxx/node-vjoy/HEAD/native/x64/vJoyInterface.dll -------------------------------------------------------------------------------- /native/x64/vJoyInterface.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangxx/node-vjoy/HEAD/native/x64/vJoyInterface.lib -------------------------------------------------------------------------------- /native/ia32/vJoyInterface.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangxx/node-vjoy/HEAD/native/ia32/vJoyInterface.dll -------------------------------------------------------------------------------- /native/ia32/vJoyInterface.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangxx/node-vjoy/HEAD/native/ia32/vJoyInterface.lib -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyDevice from "./lib/vjoy_device"; 2 | 3 | export { vJoyDevice }; 4 | export { vJoy } from "./lib/vjoy"; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | vJoy: require('./lib/vjoy'), // general functions 3 | vJoyDevice: require('./lib/vjoy_device'), // device abstraction 4 | } -------------------------------------------------------------------------------- /lib/ffb_manager.d.ts: -------------------------------------------------------------------------------- 1 | declare class FFBManager { 2 | register(deviceId: number, callback: any): void; 3 | unregister(deviceId: number): void; 4 | } 5 | 6 | declare const _exports: FFBManager; 7 | export = _exports; -------------------------------------------------------------------------------- /include/gen-versioninfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Version as four nibbles as X.H.M.L 3 | #define VER_X_ 2 4 | #define VER_H_ 2 5 | #define VER_M_ 1 6 | #define VER_L_ 1 7 | // Internal use only 8 | #define BUILD 5 9 | 10 | #define FILEVER "2, 2, 1, 1" 11 | #define PRODVER_TAG "v2.2.1.1" 12 | #define PRODVER_SHA1 "28bede" 13 | 14 | -------------------------------------------------------------------------------- /lib/vjoy_axis.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyDevice from "./vjoy_device"; 2 | 3 | declare class vJoyAxis { 4 | constructor(parent: vJoyDevice, axisId: string); 5 | 6 | get id(): string; 7 | get value(): number; 8 | 9 | /** 10 | * Set the value of the axis between 1 and 32769 (0x8000) 11 | * @param {Number} value 12 | * @returns {Boolean} 13 | */ 14 | set(value: number): boolean; 15 | } 16 | 17 | export = vJoyAxis; -------------------------------------------------------------------------------- /lib/vjoy_button.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyDevice from "./vjoy_device"; 2 | 3 | declare class vJoyButton { 4 | constructor(parent: vJoyDevice, buttonId: number); 5 | 6 | get id(): number; 7 | get value(): boolean; 8 | 9 | /** 10 | * Set this button to be pressed or not pressed 11 | * @param {Boolean} value 12 | * @returns {Boolean} 13 | */ 14 | set(value: boolean): boolean; 15 | } 16 | 17 | export = vJoyButton; -------------------------------------------------------------------------------- /include/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include -------------------------------------------------------------------------------- /lib/vjoy_disc_pov.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyDevice from "./vjoy_device"; 2 | 3 | declare class vJoyDiscretePOV { 4 | constructor(parent: vJoyDevice, povId: number); 5 | 6 | get id(): number; 7 | get value(): number; 8 | 9 | /** 10 | * Set the direction of the POV switch. 11 | * 0 = Up 12 | * 1 = Right 13 | * 2 = Down 14 | * 3 = Left 15 | * -1 = Neutral 16 | * @param {Number} value 17 | * @returns {Boolean} 18 | */ 19 | set(value: number): boolean; 20 | } 21 | 22 | export = vJoyDiscretePOV; -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | // https://stackoverflow.com/questions/215963/how-do-you-properly-use-widechartomultibyte 4 | std::string utf8_encode(const std::wstring &wstr) 5 | { 6 | if(wstr.empty()) { 7 | return std::string(); 8 | } 9 | 10 | int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); 11 | std::string strTo(size_needed, 0); 12 | WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); 13 | 14 | return strTo; 15 | } -------------------------------------------------------------------------------- /lib/vjoy_cont_pov.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyDevice from "./vjoy_device"; 2 | 3 | declare class vJoyContinuousPOV { 4 | constructor(parent: vJoyDevice, povId: number); 5 | 6 | get id(): number; 7 | get value(): number; 8 | 9 | /** 10 | * Set the direction of the POV switch. Value has to be between -1 and 35999 where 11 | * -1 = Neutral 12 | * 0 = Up 13 | * 9000 = Right 14 | * 18000 = Down 15 | * 27000 = Left 16 | * @param {Number} value 17 | * @returns {Boolean} 18 | */ 19 | set(value: number): boolean; 20 | } 21 | 22 | export = vJoyContinuousPOV; -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../include/stdafx.h" 4 | #include "../include/public.h" 5 | #include "../include/vjoyinterface.h" 6 | 7 | std::string utf8_encode(const std::wstring &wstr); 8 | 9 | const static std::unordered_map AxisMap{ 10 | { "X", HID_USAGE_X }, 11 | { "Y", HID_USAGE_Y }, 12 | { "Z", HID_USAGE_Z }, 13 | { "Rx", HID_USAGE_RX }, 14 | { "Ry", HID_USAGE_RY }, 15 | { "Rz", HID_USAGE_RZ }, 16 | { "Slider0", HID_USAGE_SL0 }, 17 | { "Slider1", HID_USAGE_SL1 }, 18 | { "Wheel", HID_USAGE_WHL }, 19 | { "POV", HID_USAGE_POV }, 20 | }; -------------------------------------------------------------------------------- /src/device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "napi.h" 4 | #include "../include/stdafx.h" 5 | #include "../include/public.h" 6 | #include "../include/vjoyinterface.h" 7 | 8 | Napi::Number wrap_GetVJDButtonNumber(const Napi::CallbackInfo& info); 9 | 10 | Napi::Number wrap_GetVJDDiscPovNumber(const Napi::CallbackInfo& info); 11 | 12 | Napi::Number wrap_GetVJDContPovNumber(const Napi::CallbackInfo& info); 13 | 14 | Napi::Boolean wrap_GetVJDAxisExists(const Napi::CallbackInfo& info); 15 | 16 | Napi::String wrap_GetVJDStatus(const Napi::CallbackInfo& info); 17 | 18 | Napi::Boolean wrap_isVJDExists(const Napi::CallbackInfo& info); -------------------------------------------------------------------------------- /examples/display_info.ts: -------------------------------------------------------------------------------- 1 | import { vJoy } from "../"; 2 | 3 | console.log("vJoy Information"); 4 | console.log(); 5 | console.log(`Enabled: ${vJoy.isEnabled()}`); 6 | console.log(`Version: ${vJoy.version()}`); 7 | console.log(`Product: ${vJoy.productString()}`); 8 | console.log(`Manufacturer: ${vJoy.manufacturerString()}`); 9 | console.log(`Serial Number: ${vJoy.serialString()}`); 10 | 11 | let match = vJoy.driverMatch(); 12 | 13 | console.log(`Driver version match: ${match.matches}`); 14 | console.log(`SDK version:: ${match.dll_version}`); 15 | 16 | console.log(`Max Devices: ${vJoy.maxDevices()}`); 17 | console.log(`Current Devices: ${vJoy.existingDevices()}`); -------------------------------------------------------------------------------- /examples/display_info.js: -------------------------------------------------------------------------------- 1 | const { vJoy } = require('../'); 2 | 3 | console.log("vJoy Information"); 4 | console.log(); 5 | console.log(`Enabled: ${vJoy.isEnabled()}`); 6 | console.log(`Version: ${vJoy.version()}`); 7 | console.log(`Product: ${vJoy.productString()}`); 8 | console.log(`Manufacturer: ${vJoy.manufacturerString()}`); 9 | console.log(`Serial Number: ${vJoy.serialString()}`); 10 | 11 | let match = vJoy.driverMatch(); 12 | 13 | console.log(`Driver version match: ${match.matches}`); 14 | console.log(`SDK version:: ${match.dll_version}`); 15 | 16 | console.log(`Max Devices: ${vJoy.maxDevices()}`); 17 | console.log(`Current Devices: ${vJoy.existingDevices()}`); -------------------------------------------------------------------------------- /lib/vjoy.d.ts: -------------------------------------------------------------------------------- 1 | type RemovalCallbackData = { 2 | removed: boolean, 3 | first: boolean 4 | } 5 | 6 | type DriverMatchData = { 7 | matches: boolean, 8 | dll_version: number, 9 | drv_version: number, 10 | } 11 | 12 | export type AxisName = "X" | "Y" | "Z" | "Rx" | "Ry" | "Rz" | "Slider0" | "Slider1" | "Wheel" | "POV"; 13 | 14 | export module vJoy { 15 | function isEnabled(): boolean; 16 | function version(): number; 17 | function productString(): string; 18 | function manufacturerString(): string; 19 | function serialString(): string; 20 | function driverMatch(): DriverMatchData; 21 | function maxDevices(): number; 22 | function existingDevices(): number; 23 | function registerRemovalCB(callback: (evt: RemovalCallbackData) => void): void; 24 | const axis: readonly AxisName[]; 25 | } -------------------------------------------------------------------------------- /src/feeding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "napi.h" 4 | #include "../include/stdafx.h" 5 | #include "../include/public.h" 6 | #include "../include/vjoyinterface.h" 7 | 8 | Napi::Boolean wrap_AcquireVJD(const Napi::CallbackInfo& info); 9 | 10 | void wrap_RelinquishVJD(const Napi::CallbackInfo& info); 11 | 12 | Napi::Boolean wrap_ResetVJD(const Napi::CallbackInfo& info); 13 | 14 | Napi::Boolean wrap_ResetButtons(const Napi::CallbackInfo& info); 15 | 16 | Napi::Boolean wrap_ResetPovs(const Napi::CallbackInfo& info); 17 | 18 | Napi::Boolean wrap_SetAxis(const Napi::CallbackInfo& info); 19 | 20 | Napi::Boolean wrap_SetBtn(const Napi::CallbackInfo& info); 21 | 22 | Napi::Boolean wrap_SetDiscPov(const Napi::CallbackInfo& info); 23 | 24 | Napi::Boolean wrap_SetContPov(const Napi::CallbackInfo& info); -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "vjoy", 5 | "sources": [ 6 | "src/utils.cpp", 7 | "src/general.cpp", 8 | "src/device.cpp", 9 | "src/feeding.cpp", 10 | "src/ffb.cpp", 11 | "src/vjoy.cpp" 12 | ], 13 | "include_dirs": [ 14 | "<(module_root_dir)/include", 15 | " 0x8000) { 35 | throw new Error("Invalid value"); 36 | } 37 | 38 | let result = vjoy_native.SetAxis(this._parent.id, this.id, value); 39 | 40 | if (result) { 41 | this._value = value; 42 | } 43 | 44 | return result; 45 | } 46 | } 47 | 48 | module.exports = vJoyAxis; -------------------------------------------------------------------------------- /lib/ffb_manager.js: -------------------------------------------------------------------------------- 1 | const vjoy_native = require('../build/Release/vjoy'); 2 | 3 | class FFBManager { 4 | constructor() { 5 | this._registeredCallbacks = {}; 6 | this._driverRegistered = false; 7 | } 8 | 9 | _handleEvent(deviceId, evt) { 10 | if (deviceId in this._registeredCallbacks) { 11 | this._registeredCallbacks[deviceId].call(null, evt); 12 | } 13 | } 14 | 15 | register(deviceId, callback) { 16 | // only register with the driver after a device wants to get FFB events 17 | if (!this._driverRegistered) { 18 | vjoy_native.FfbRegisterGenCB((id, evt) => { 19 | // run code in the main thread to be able to handle errors 20 | process.nextTick(() => { 21 | this._handleEvent(id, evt); 22 | }); 23 | }); 24 | this._driverRegistered = true; 25 | } 26 | 27 | this._registeredCallbacks[deviceId] = callback; 28 | } 29 | 30 | unregister(deviceId) { 31 | if (deviceId in this._registeredCallbacks) { 32 | delete this._registeredCallbacks[deviceId]; 33 | } 34 | } 35 | } 36 | 37 | module.exports = new FFBManager(); -------------------------------------------------------------------------------- /src/ffb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "napi.h" 4 | #include "../include/stdafx.h" 5 | #include "../include/public.h" 6 | #include "../include/vjoyinterface.h" 7 | #include 8 | 9 | // Napi::Boolean wrap_FfbStart(const Napi::CallbackInfo& info); 10 | 11 | // void wrap_FfbStop(const Napi::CallbackInfo& info); 12 | 13 | Napi::Value wrap_Ffb_h_Eff_Report(Napi::Env env, const FFB_DATA* packet); 14 | Napi::Value wrap_Ffb_h_Eff_Ramp(Napi::Env env, const FFB_DATA* packet); 15 | Napi::Value wrap_Ffb_h_EffOp(Napi::Env env, const FFB_DATA* packet); 16 | Napi::Value wrap_Ffb_h_Eff_Period(Napi::Env env, const FFB_DATA* packet); 17 | Napi::Value wrap_Ffb_h_Eff_Cond(Napi::Env env, const FFB_DATA* packet); 18 | Napi::Value wrap_Ffb_h_Eff_Envlp(Napi::Env env, const FFB_DATA* packet); 19 | Napi::Value wrap_Ffb_h_Eff_Constant(Napi::Env env, const FFB_DATA* packet); 20 | 21 | void wrap_FfbRegisterGenCB(const Napi::CallbackInfo& info); 22 | 23 | Napi::Boolean wrap_IsDeviceFfb(const Napi::CallbackInfo& info); 24 | 25 | Napi::Object getDeviceFfbEffects(const Napi::CallbackInfo& info); -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jan Scheiper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/vjoy_disc_pov.js: -------------------------------------------------------------------------------- 1 | const vjoy_native = require('../build/Release/vjoy'); 2 | 3 | class vJoyDiscretePOV { 4 | constructor(parent, povId) { 5 | this._parent = parent; 6 | 7 | this._id = povId; 8 | this._value = -1; 9 | } 10 | 11 | get id() { 12 | return this._id; 13 | } 14 | 15 | get value() { 16 | return this._value; 17 | } 18 | 19 | /** 20 | * Reset the internal value of the POV abstraction 21 | */ 22 | _reset() { 23 | this._value = -1; 24 | } 25 | 26 | /** 27 | * Set the direction of the POV switch. 28 | * 0 = Up 29 | * 1 = Right 30 | * 2 = Down 31 | * 3 = Left 32 | * -1 = Neutral 33 | * @param {Number} value 34 | * @returns {Boolean} 35 | */ 36 | set(value) { 37 | if (!this._parent.initialized) throw new Error("Device is not initialized"); 38 | 39 | if (![-1, 0, 1, 2, 3].includes(value)) { 40 | throw new Error("Invalid value"); 41 | } 42 | 43 | let result = vjoy_native.SetDiscPov(this._parent.id, this.id, value); 44 | 45 | if (result) { 46 | this._value = value; 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | 53 | module.exports = vJoyDiscretePOV; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vjoy", 3 | "version": "1.3.0", 4 | "description": "Native Node bindings for the vjoy virtual joystick driver", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "install": "node-gyp rebuild", 12 | "build": "node-gyp build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/jangxx/node-vjoy.git" 17 | }, 18 | "keywords": [ 19 | "vjoy", 20 | "bindings", 21 | "native", 22 | "virtual", 23 | "gamepad" 24 | ], 25 | "author": "jangxx ", 26 | "license": "MIT", 27 | "gypfile": true, 28 | "bugs": { 29 | "url": "https://github.com/jangxx/node-vjoy/issues" 30 | }, 31 | "homepage": "https://github.com/jangxx/node-vjoy#readme", 32 | "dependencies": { 33 | "node-addon-api": "^2.0.2" 34 | }, 35 | "engines": { 36 | "node": ">=8.0.0" 37 | }, 38 | "os": [ 39 | "win32" 40 | ], 41 | "cpu": [ 42 | "x64", 43 | "ia32" 44 | ], 45 | "types": "index.d.ts", 46 | "devDependencies": { 47 | "@types/node": "^20.5.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/vjoy_cont_pov.js: -------------------------------------------------------------------------------- 1 | const vjoy_native = require('../build/Release/vjoy'); 2 | 3 | class vJoyContinuousPOV { 4 | constructor(parent, povId) { 5 | this._parent = parent; 6 | 7 | this._id = povId; 8 | this._value = -1; 9 | } 10 | 11 | get id() { 12 | return this._id; 13 | } 14 | 15 | get value() { 16 | return this._value; 17 | } 18 | 19 | /** 20 | * Reset the internal value of the POV abstraction 21 | */ 22 | _reset() { 23 | this._value = -1; 24 | } 25 | 26 | /** 27 | * Set the direction of the POV switch. Value has to be between -1 and 35999 where 28 | * -1 = Neutral 29 | * 0 = Up 30 | * 9000 = Right 31 | * 18000 = Down 32 | * 27000 = Left 33 | * @param {Number} value 34 | * @returns {Boolean} 35 | */ 36 | set(value) { 37 | if (!this._parent.initialized) throw new Error("Device is not initialized"); 38 | 39 | if (value < -1 || value > 35999) { 40 | throw new Error("Invalid value"); 41 | } 42 | 43 | let result = vjoy_native.SetContPov(this._parent.id, this.id, value); 44 | 45 | if (result) { 46 | this._value = value; 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | 53 | module.exports = vJoyContinuousPOV; -------------------------------------------------------------------------------- /examples/feeder.js: -------------------------------------------------------------------------------- 1 | const { vJoyDevice, vJoy } = require('../'); 2 | 3 | let deviceId = 1; 4 | 5 | if (process.argv.length > 2) { 6 | deviceId = Number(process.argv[2]); 7 | } 8 | 9 | if (!vJoy.isEnabled()) { 10 | console.log("vJoy is not enabled."); 11 | process.exit(); 12 | } 13 | 14 | let device = vJoyDevice.create(deviceId); 15 | 16 | if (device == null) { 17 | console.log(`Could not initialize the device. Status: ${vJoyDevice.status(deviceId)}`); 18 | process.exit(); 19 | } 20 | 21 | let axisVal = 0; 22 | let activeButton = 0; 23 | 24 | setInterval(() => { 25 | axisVal = modClamp(axisVal + 200, 1, 0x8000); 26 | activeButton = modClamp(activeButton + 1, 1, 9); 27 | 28 | // set X, Y and Z axes 29 | if (device.axes.Z != null) { 30 | device.axes.Z.set(axisVal); 31 | } 32 | if (device.axes.X != null) { 33 | device.axes.X.set(modClamp(32000 - axisVal, 1, 0x8000)); 34 | } 35 | if (device.axes.Y != null) { 36 | device.axes.Y.set(modClamp(Math.round(axisVal/2) + 7000, 1, 0x8000)); 37 | } 38 | 39 | // set first 8 buttons 40 | device.resetButtons(); 41 | 42 | if (device.buttons[activeButton] != undefined) { 43 | device.buttons[activeButton].set(true); 44 | } 45 | }, 100); 46 | 47 | function modClamp(value, lower, upper) { 48 | let diff = upper - lower; 49 | value -= lower; 50 | 51 | return lower + ((value % diff) + diff) % diff; 52 | } -------------------------------------------------------------------------------- /examples/feeder.ts: -------------------------------------------------------------------------------- 1 | import { vJoyDevice, vJoy } from "../"; 2 | 3 | let deviceId = 1; 4 | 5 | if (process.argv.length > 2) { 6 | deviceId = Number(process.argv[2]); 7 | } 8 | 9 | if (!vJoy.isEnabled()) { 10 | console.log("vJoy is not enabled."); 11 | process.exit(); 12 | } 13 | 14 | let device = vJoyDevice.create(deviceId); 15 | 16 | if (device == null) { 17 | console.log(`Could not initialize the device. Status: ${vJoyDevice.status(deviceId)}`); 18 | process.exit(); 19 | } 20 | 21 | let axisVal = 0; 22 | let activeButton = 0; 23 | 24 | setInterval(() => { 25 | if (!device) return; 26 | 27 | axisVal = modClamp(axisVal + 200, 1, 0x8000); 28 | activeButton = modClamp(activeButton + 1, 1, 9); 29 | 30 | // set X, Y and Z axes 31 | if (device.axes.Z != null) { 32 | device.axes.Z.set(axisVal); 33 | } 34 | if (device.axes.X != null) { 35 | device.axes.X.set(modClamp(32000 - axisVal, 1, 0x8000)); 36 | } 37 | if (device.axes.Y != null) { 38 | device.axes.Y.set(modClamp(Math.round(axisVal/2) + 7000, 1, 0x8000)); 39 | } 40 | 41 | // set first 8 buttons 42 | device.resetButtons(); 43 | 44 | if (device.buttons[activeButton] != undefined) { 45 | device.buttons[activeButton].set(true); 46 | } 47 | }, 100); 48 | 49 | function modClamp(value: number, lower: number, upper: number) { 50 | let diff = upper - lower; 51 | value -= lower; 52 | 53 | return lower + ((value % diff) + diff) % diff; 54 | } -------------------------------------------------------------------------------- /examples/force_feedback.js: -------------------------------------------------------------------------------- 1 | const { vJoyDevice, vJoy } = require('../'); 2 | 3 | let deviceId = 1; 4 | 5 | if (process.argv.length > 2) { 6 | deviceId = Number(process.argv[2]); 7 | } 8 | 9 | if (!vJoy.isEnabled()) { 10 | console.log("vJoy is not enabled."); 11 | process.exit(); 12 | } 13 | 14 | let device = vJoyDevice.create(deviceId); 15 | 16 | if (device == null) { 17 | console.log(`Could not initialize the device. Status: ${vJoyDevice.status(deviceId)}`); 18 | process.exit(); 19 | } 20 | 21 | console.log("FFB supported:", device.ffbSupported); 22 | console.log("Supported effects:"); 23 | 24 | let effects = device.ffbEffects; 25 | for (let effect in effects) { 26 | console.log(` ${effect}: ${effects[effect]}`); 27 | } 28 | 29 | device.enableFFBEvents(); 30 | 31 | console.log("Captured events:"); 32 | console.log("----------------"); 33 | 34 | device.on("ffb", evt => { 35 | console.log(`Event ${evt.type}`); 36 | 37 | if (evt.effect != null) { 38 | console.log(" Effect data:"); 39 | for (let key in evt.effect) { 40 | console.log(` ${key}: ${evt.effect[key]}`); 41 | } 42 | } 43 | 44 | if (evt.ebi != null) { 45 | console.log(` Effect Block Index: ${evt.ebi}`); 46 | } 47 | 48 | if (evt.gain != null) { 49 | console.log(` Gain: ${evt.gain}`); 50 | } 51 | 52 | if (evt.control != null) { 53 | console.log(` Control: ${evt.control}`); 54 | } 55 | 56 | console.log(); 57 | }); -------------------------------------------------------------------------------- /lib/vjoy.js: -------------------------------------------------------------------------------- 1 | const vjoy_native = require('../build/Release/vjoy'); 2 | 3 | // display warning if driver version does not match 4 | let check = vjoy_native.DriverMatch(); 5 | 6 | if (!check.matches && !("DISABLE_VJOY_VERSION_CHECK" in process.env)) { 7 | if (check.dll_version > check.drv_version) { 8 | console.warn(`WARNING: The installed version (${check.drv_version}) of vJoy is older than the SDK this library was built against (${check.dll_version}). If you run into problems, please update your vJoy installation.`); 9 | } else { 10 | console.warn(`WARNING: The installed version (${check.drv_version}) of vJoy is newer than the SDK this library was built against (${check.dll_version}). Please notify the author so he can update the library.`); 11 | } 12 | } 13 | 14 | const AXES = Object.freeze([ "X", "Y", "Z", "Rx", "Ry", "Rz", "Slider0", "Slider1", "Wheel", "POV" ]); 15 | 16 | function registerRemovalCB(callback) { 17 | vjoy_native.RegisterRemovalCB(function(evt) { 18 | process.nextTick(() => { 19 | callback(evt); 20 | }); 21 | }); 22 | } 23 | 24 | module.exports = { 25 | isEnabled: vjoy_native.vJoyEnabled, 26 | version: vjoy_native.GetvJoyVersion, 27 | productString: vjoy_native.GetvJoyProductString, 28 | manufacturerString: vjoy_native.GetvJoyManufacturerString, 29 | serialString: vjoy_native.GetvJoySerialNumberString, 30 | driverMatch: vjoy_native.DriverMatch, 31 | maxDevices: vjoy_native.GetvJoyMaxDevices, 32 | existingDevices: vjoy_native.GetNumberExistingVJD, 33 | axes: AXES, 34 | registerRemovalCB, 35 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | # Edit at https://www.gitignore.io/?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # vuepress build output 84 | .vuepress/dist 85 | 86 | # Serverless directories 87 | .serverless/ 88 | 89 | # FuseBox cache 90 | .fusebox/ 91 | 92 | # DynamoDB Local files 93 | .dynamodb/ 94 | 95 | build/ 96 | 97 | # local vscode config 98 | .vscode -------------------------------------------------------------------------------- /src/device.cpp: -------------------------------------------------------------------------------- 1 | #include "device.h" 2 | #include "utils.h" 3 | 4 | Napi::Number wrap_GetVJDButtonNumber(const Napi::CallbackInfo& info) { 5 | Napi::Env env = info.Env(); 6 | 7 | UINT rID = info[0].As().Uint32Value(); 8 | 9 | int buttonNumber = GetVJDButtonNumber(rID); 10 | 11 | return Napi::Number::New(env, (double)buttonNumber); 12 | } 13 | 14 | Napi::Number wrap_GetVJDDiscPovNumber(const Napi::CallbackInfo& info) { 15 | Napi::Env env = info.Env(); 16 | 17 | UINT rID = info[0].As().Uint32Value(); 18 | 19 | int discPovNumber = GetVJDDiscPovNumber(rID); 20 | 21 | return Napi::Number::New(env, (double)discPovNumber); 22 | } 23 | 24 | Napi::Number wrap_GetVJDContPovNumber(const Napi::CallbackInfo& info) { 25 | Napi::Env env = info.Env(); 26 | 27 | UINT rID = info[0].As().Uint32Value(); 28 | 29 | int contPovNumber = GetVJDDiscPovNumber(rID); 30 | 31 | return Napi::Number::New(env, (double)contPovNumber); 32 | } 33 | 34 | Napi::Boolean wrap_GetVJDAxisExists(const Napi::CallbackInfo& info) { 35 | Napi::Env env = info.Env(); 36 | 37 | UINT rID = info[0].As().Uint32Value(); 38 | std::string axis = info[1].As().ToString(); 39 | 40 | if (AxisMap.count(axis) == 0) { 41 | Napi::Error::New(env, "Invalid axis").ThrowAsJavaScriptException(); 42 | return Napi::Boolean::Boolean(); 43 | } 44 | 45 | UINT axisID = AxisMap.at(axis); 46 | 47 | bool exists = GetVJDAxisExist(rID, axisID); 48 | 49 | return Napi::Boolean::New(env, exists); 50 | } 51 | 52 | Napi::String wrap_GetVJDStatus(const Napi::CallbackInfo& info) { 53 | Napi::Env env = info.Env(); 54 | 55 | UINT rID = info[0].As().Uint32Value(); 56 | 57 | VjdStat status = GetVJDStatus(rID); 58 | 59 | switch (status) { 60 | case VJD_STAT_OWN: 61 | return Napi::String::New(env, "VJD_STAT_OWN"); 62 | case VJD_STAT_FREE: 63 | return Napi::String::New(env, "VJD_STAT_FREE"); 64 | case VJD_STAT_BUSY: 65 | return Napi::String::New(env, "VJD_STAT_BUSY"); 66 | case VJD_STAT_MISS: 67 | return Napi::String::New(env, "VJD_STAT_MISS"); 68 | case VJD_STAT_UNKN: 69 | default: 70 | return Napi::String::New(env, "VJD_STAT_UNKN"); 71 | } 72 | } 73 | 74 | Napi::Boolean wrap_isVJDExists(const Napi::CallbackInfo& info) { 75 | Napi::Env env = info.Env(); 76 | 77 | UINT rID = info[0].As().Uint32Value(); 78 | 79 | bool exists = isVJDExists(rID); 80 | 81 | return Napi::Boolean::New(env, exists); 82 | } -------------------------------------------------------------------------------- /src/vjoy.cpp: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "../include/stdafx.h" 3 | #include "../include/public.h" 4 | #include "../include/vjoyinterface.h" 5 | 6 | #include "general.h" 7 | #include "device.h" 8 | #include "feeding.h" 9 | #include "ffb.h" 10 | 11 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 12 | exports.Set("vJoyEnabled", Napi::Function::New(env, wrap_vJoyEnabled)); 13 | exports.Set("GetvJoyVersion", Napi::Function::New(env, wrap_GetvJoyVersion)); 14 | exports.Set("GetvJoyProductString", Napi::Function::New(env, wrap_GetvJoyProductString)); 15 | exports.Set("GetvJoyManufacturerString", Napi::Function::New(env, wrap_GetvJoyManufacturerString)); 16 | exports.Set("GetvJoySerialNumberString", Napi::Function::New(env, wrap_GetvJoySerialNumberString)); 17 | exports.Set("DriverMatch", Napi::Function::New(env, wrap_DriverMatch)); 18 | exports.Set("GetvJoyMaxDevices", Napi::Function::New(env, wrap_GetvJoyMaxDevices)); 19 | exports.Set("GetNumberExistingVJD", Napi::Function::New(env, wrap_GetNumberExistingVJD)); 20 | exports.Set("RegisterRemovalCB", Napi::Function::New(env, wrap_RegisterRemovalCB)); 21 | 22 | exports.Set("GetVJDButtonNumber", Napi::Function::New(env, wrap_GetVJDButtonNumber)); 23 | exports.Set("GetVJDDiscPovNumber", Napi::Function::New(env, wrap_GetVJDDiscPovNumber)); 24 | exports.Set("GetVJDContPovNumber", Napi::Function::New(env, wrap_GetVJDContPovNumber)); 25 | exports.Set("GetVJDAxisExists", Napi::Function::New(env, wrap_GetVJDAxisExists)); 26 | exports.Set("GetVJDStatus", Napi::Function::New(env, wrap_GetVJDStatus)); 27 | exports.Set("isVJDExists", Napi::Function::New(env, wrap_isVJDExists)); 28 | 29 | exports.Set("AcquireVJD", Napi::Function::New(env, wrap_AcquireVJD)); 30 | exports.Set("RelinquishVJD", Napi::Function::New(env, wrap_RelinquishVJD)); 31 | exports.Set("ResetVJD", Napi::Function::New(env, wrap_ResetVJD)); 32 | exports.Set("ResetButtons", Napi::Function::New(env, wrap_ResetButtons)); 33 | exports.Set("ResetPovs", Napi::Function::New(env, wrap_ResetPovs)); 34 | exports.Set("SetAxis", Napi::Function::New(env, wrap_SetAxis)); 35 | exports.Set("SetBtn", Napi::Function::New(env, wrap_SetBtn)); 36 | exports.Set("SetDiscPov", Napi::Function::New(env, wrap_SetDiscPov)); 37 | exports.Set("SetContPov", Napi::Function::New(env, wrap_SetContPov)); 38 | 39 | exports.Set("IsDeviceFfb", Napi::Function::New(env, wrap_IsDeviceFfb)); 40 | exports.Set("FfbRegisterGenCB", Napi::Function::New(env, wrap_FfbRegisterGenCB)); 41 | exports.Set("getDeviceFfbEffects", Napi::Function::New(env, getDeviceFfbEffects)); 42 | 43 | return exports; 44 | } 45 | 46 | NODE_API_MODULE(vjoy, Init) -------------------------------------------------------------------------------- /lib/vjoy_device.d.ts: -------------------------------------------------------------------------------- 1 | import vJoyButton from "./vjoy_button"; 2 | import vJoyContinuousPOV from "./vjoy_cont_pov"; 3 | import vJoyDiscretePOV from "./vjoy_disc_pov"; 4 | import { AxisName } from "./vjoy"; 5 | import vJoyAxis from "./vjoy_axis"; 6 | 7 | declare class vJoyDevice { 8 | /** 9 | * Convenience method to create and initialize a new vJoyDevice 10 | * @param {Number} id 11 | * @return {vJoyDevice} 12 | */ 13 | static create(id: number): vJoyDevice | null; 14 | /** 15 | * Check if a vJoy device id exists 16 | * @param {Number} id 17 | * @returns {Boolean} 18 | */ 19 | static exists(id: number): boolean; 20 | /** 21 | * Check the status of a vJoy device 22 | * @param {Number} id 23 | * @returns {String} 24 | */ 25 | static status(id: number): "busy" | "missing" | "free" | "acquired" | "unknown"; 26 | /** 27 | * Check if a vJoy device supports Force Feedback 28 | * @param {Number} id 29 | * @returns {Boolean} 30 | */ 31 | static ffbSupported(id: number): boolean; 32 | /** 33 | * Get an object containing information about the supported FFB effect types 34 | * @param {Number} id 35 | * @returns {Object} 36 | */ 37 | static getFFBEffects(id: number): object; 38 | 39 | constructor(id: any); 40 | get id(): number; 41 | get initialized(): boolean; 42 | get buttons(): Readonly>; 43 | get discretePOV(): Readonly>; 44 | get continuousPOV(): Readonly>; 45 | get axes(): Readonly>; 46 | get status(): string; 47 | get ffbSupported(): boolean; 48 | get ffbEffects(): any; 49 | 50 | /** 51 | * Initializes the device by checking its status and acquriring it if possible. 52 | * Returns true if the initialization was successful. 53 | * @returns {Boolean} 54 | */ 55 | initialize(): boolean; 56 | /** 57 | * Query the driver for configured axes, buttons and POV switches. 58 | * This also resets all button and POV values 59 | */ 60 | updateInputs(): void; 61 | /** 62 | * Sets all buttons to not pressed 63 | * @returns {Boolean} 64 | */ 65 | resetButtons(): boolean; 66 | /** 67 | * Relinquishes the device. Do not forget to relinquish a device if you want other feeders to access it. 68 | */ 69 | free(): void; 70 | /** 71 | * Enables the emission of Force Feedback events 72 | */ 73 | enableFFBEvents(): void; 74 | /** 75 | * Disables the emission of Force Feedback events 76 | */ 77 | disableFFBEvents(): void; 78 | } 79 | 80 | export = vJoyDevice; -------------------------------------------------------------------------------- /src/feeding.cpp: -------------------------------------------------------------------------------- 1 | #include "feeding.h" 2 | #include "utils.h" 3 | 4 | Napi::Boolean wrap_AcquireVJD(const Napi::CallbackInfo& info) { 5 | Napi::Env env = info.Env(); 6 | 7 | UINT rID = info[0].As().Uint32Value(); 8 | 9 | bool result = AcquireVJD(rID); 10 | 11 | return Napi::Boolean::New(env, result); 12 | } 13 | 14 | void wrap_RelinquishVJD(const Napi::CallbackInfo& info) { 15 | Napi::Env env = info.Env(); 16 | 17 | UINT rID = info[0].As().Uint32Value(); 18 | 19 | RelinquishVJD(rID); 20 | } 21 | 22 | Napi::Boolean wrap_ResetVJD(const Napi::CallbackInfo& info) { 23 | Napi::Env env = info.Env(); 24 | 25 | UINT rID = info[0].As().Uint32Value(); 26 | 27 | bool result = ResetVJD(rID); 28 | 29 | return Napi::Boolean::New(env, result); 30 | } 31 | 32 | Napi::Boolean wrap_ResetButtons(const Napi::CallbackInfo& info) { 33 | Napi::Env env = info.Env(); 34 | 35 | UINT rID = info[0].As().Uint32Value(); 36 | 37 | bool result = ResetButtons(rID); 38 | 39 | return Napi::Boolean::New(env, result); 40 | } 41 | 42 | Napi::Boolean wrap_ResetPovs(const Napi::CallbackInfo& info) { 43 | Napi::Env env = info.Env(); 44 | 45 | UINT rID = info[0].As().Uint32Value(); 46 | 47 | bool result = ResetPovs(rID); 48 | 49 | return Napi::Boolean::New(env, result); 50 | } 51 | 52 | Napi::Boolean wrap_SetAxis(const Napi::CallbackInfo& info) { 53 | Napi::Env env = info.Env(); 54 | 55 | UINT rID = info[0].As().Uint32Value(); 56 | std::string axis = info[1].As().ToString(); 57 | 58 | long value = info[2].As().Uint32Value(); 59 | 60 | if (AxisMap.count(axis) == 0) { 61 | Napi::Error::New(env, "Invalid axis").ThrowAsJavaScriptException(); 62 | return Napi::Boolean::Boolean(); 63 | } 64 | 65 | if (value < 1 || value > 0x8000) { 66 | Napi::Error::New(env, "Axis value out of range").ThrowAsJavaScriptException(); 67 | return Napi::Boolean::Boolean(); 68 | } 69 | 70 | UINT axisID = AxisMap.at(axis); 71 | 72 | bool result = SetAxis(value, rID, axisID); 73 | 74 | return Napi::Boolean::New(env, result); 75 | } 76 | 77 | Napi::Boolean wrap_SetBtn(const Napi::CallbackInfo& info) { 78 | Napi::Env env = info.Env(); 79 | 80 | UINT rID = info[0].As().Uint32Value(); 81 | uint8_t button = info[1].As().Uint32Value(); 82 | 83 | bool value = info[2].As().ToBoolean(); 84 | 85 | if (button < 1 || button > 128) { 86 | Napi::Error::New(env, "Button number out of range").ThrowAsJavaScriptException(); 87 | return Napi::Boolean::Boolean(); 88 | } 89 | 90 | bool result = SetBtn(value, rID, button); 91 | 92 | return Napi::Boolean::New(env, result); 93 | } 94 | 95 | Napi::Boolean wrap_SetDiscPov(const Napi::CallbackInfo& info) { 96 | Napi::Env env = info.Env(); 97 | 98 | UINT rID = info[0].As().Uint32Value(); 99 | uint8_t pov = info[1].As().Uint32Value(); 100 | 101 | int value = info[2].As().Int32Value(); 102 | 103 | if (pov < 1 || pov > 4) { 104 | Napi::Error::New(env, "POV number out of range").ThrowAsJavaScriptException(); 105 | return Napi::Boolean::Boolean(); 106 | } 107 | 108 | bool result = SetDiscPov(value, rID, pov); 109 | 110 | return Napi::Boolean::New(env, result); 111 | } 112 | 113 | Napi::Boolean wrap_SetContPov(const Napi::CallbackInfo& info) { 114 | Napi::Env env = info.Env(); 115 | 116 | UINT rID = info[0].As().Uint32Value(); 117 | uint8_t pov = info[1].As().Uint32Value(); 118 | 119 | int value = info[2].As().Int32Value(); 120 | 121 | if (pov < 1 || pov > 4) { 122 | Napi::Error::New(env, "POV number out of range").ThrowAsJavaScriptException(); 123 | return Napi::Boolean::Boolean(); 124 | } 125 | 126 | bool result = SetContPov(value, rID, pov); 127 | 128 | return Napi::Boolean::New(env, result); 129 | } -------------------------------------------------------------------------------- /src/general.cpp: -------------------------------------------------------------------------------- 1 | #include "general.h" 2 | #include "utils.h" 3 | 4 | Napi::ThreadSafeFunction removalCB_function = nullptr; 5 | 6 | Napi::Boolean wrap_vJoyEnabled(const Napi::CallbackInfo& info) { 7 | Napi::Env env = info.Env(); 8 | 9 | bool enabled = vJoyEnabled(); 10 | 11 | return Napi::Boolean::New(env, enabled); 12 | } 13 | 14 | Napi::Number wrap_GetvJoyVersion(const Napi::CallbackInfo& info) { 15 | Napi::Env env = info.Env(); 16 | 17 | short version = GetvJoyVersion(); 18 | 19 | return Napi::Number::New(env, (double)version); 20 | } 21 | 22 | Napi::String wrap_GetvJoyProductString(const Napi::CallbackInfo& info) { 23 | Napi::Env env = info.Env(); 24 | 25 | PVOID p_prodString = GetvJoyProductString(); 26 | 27 | std::string prodString = utf8_encode((PWSTR)p_prodString); 28 | 29 | CoTaskMemFree(p_prodString); 30 | 31 | return Napi::String::New(env, prodString); 32 | } 33 | 34 | Napi::String wrap_GetvJoyManufacturerString(const Napi::CallbackInfo& info) { 35 | Napi::Env env = info.Env(); 36 | 37 | PVOID p_manuString = GetvJoyManufacturerString(); 38 | 39 | std::string manuString = utf8_encode((PWSTR)p_manuString); 40 | 41 | CoTaskMemFree(p_manuString); 42 | 43 | return Napi::String::New(env, manuString); 44 | } 45 | 46 | Napi::String wrap_GetvJoySerialNumberString(const Napi::CallbackInfo& info) { 47 | Napi::Env env = info.Env(); 48 | 49 | PVOID p_serialString = GetvJoySerialNumberString(); 50 | 51 | std::string serialString = utf8_encode((PWSTR)p_serialString); 52 | 53 | CoTaskMemFree(p_serialString); 54 | 55 | return Napi::String::New(env, serialString); 56 | } 57 | 58 | Napi::Object wrap_DriverMatch(const Napi::CallbackInfo& info) { 59 | Napi::Env env = info.Env(); 60 | 61 | WORD DllVersion; 62 | WORD DrvVersion; 63 | 64 | bool matches = DriverMatch(&DllVersion, &DrvVersion); 65 | 66 | Napi::Boolean var_matches = Napi::Boolean::New(env, matches); 67 | Napi::Number var_dllversion = Napi::Number::New(env, (double)DllVersion); 68 | Napi::Number var_drvversion = Napi::Number::New(env, (double)DrvVersion); 69 | 70 | Napi::Object res = Napi::Object::New(env); 71 | 72 | res.Set("matches", var_matches); 73 | res.Set("dll_version", var_dllversion); 74 | res.Set("drv_version", var_drvversion); 75 | 76 | return res; 77 | } 78 | 79 | Napi::Number wrap_GetvJoyMaxDevices(const Napi::CallbackInfo& info) { 80 | Napi::Env env = info.Env(); 81 | 82 | int maxDevices; 83 | 84 | GetvJoyMaxDevices(&maxDevices); 85 | 86 | return Napi::Number::New(env, (double)maxDevices); 87 | } 88 | 89 | Napi::Number wrap_GetNumberExistingVJD(const Napi::CallbackInfo& info) { 90 | Napi::Env env = info.Env(); 91 | 92 | int existingVJD; 93 | 94 | GetNumberExistingVJD(&existingVJD); 95 | 96 | return Napi::Number::New(env, (double)existingVJD); 97 | } 98 | 99 | void ConfChangedCB(BOOL Removed, BOOL First, PVOID _data) { 100 | RemovalCBData* data = new RemovalCBData{ Removed == TRUE, First == TRUE }; 101 | 102 | removalCB_function.NonBlockingCall(data, [](Napi::Env env, Napi::Function fn, RemovalCBData* data) { 103 | Napi::Object evt = Napi::Object::New(env); 104 | evt.Set("removed", Napi::Boolean::New(env, data->removed)); 105 | evt.Set("first", Napi::Boolean::New(env, data->first)); 106 | 107 | // pass data to JS callback 108 | fn.Call( {evt} ); 109 | 110 | delete data; // clean up 111 | }); 112 | } 113 | 114 | // This function leaks memory if called more than once which is why it's not exposed to the users 115 | void wrap_RegisterRemovalCB(const Napi::CallbackInfo& info) { 116 | Napi::Env env = info.Env(); 117 | 118 | if (!info[0].IsFunction()) { 119 | Napi::Error::New(env, "The first parameter needs to be a function").ThrowAsJavaScriptException(); 120 | return; 121 | } 122 | 123 | Napi::Function callback = info[0].As(); 124 | removalCB_function = Napi::ThreadSafeFunction::New(env, callback, "removalCB", 0, 1); 125 | 126 | RegisterRemovalCB(&ConfChangedCB, nullptr); 127 | } 128 | 129 | -------------------------------------------------------------------------------- /lib/vjoy_device.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | const vjoy_native = require('../build/Release/vjoy'); 4 | const vJoy = require('./vjoy'); 5 | const FFBManager = require('./ffb_manager'); 6 | 7 | const vJoyButton = require('./vjoy_button'); 8 | const vJoyAxis = require('./vjoy_axis'); 9 | const vJoyDiscretePOV = require('./vjoy_disc_pov'); 10 | const vJoyContinuousPOV = require('./vjoy_cont_pov'); 11 | 12 | class vJoyDevice extends EventEmitter { 13 | constructor(id) { 14 | super(); 15 | 16 | this._id = id; 17 | 18 | this._initialized = false; 19 | 20 | this._axes = {}; 21 | this._buttons = {}; 22 | this._discretePOV = {}; 23 | this._continuousPOV = {}; 24 | 25 | for (let axisId of vJoy.axes) { 26 | this._axes[axisId] = null; 27 | } 28 | } 29 | 30 | /** 31 | * Convenience method to create and initialize a new vJoyDevice 32 | * @param {Number} id 33 | * @return {vJoyDevice} 34 | */ 35 | static create(id) { 36 | if (!vJoyDevice.exists(id)) { 37 | return null; 38 | } 39 | 40 | let device = new vJoyDevice(id); 41 | 42 | let result = device.initialize(); 43 | 44 | if (result) { 45 | device.updateInputs(); 46 | 47 | return device; 48 | } else { 49 | return null; 50 | } 51 | } 52 | 53 | /** 54 | * Check if a vJoy device id exists 55 | * @param {Number} id 56 | * @returns {Boolean} 57 | */ 58 | static exists(id) { 59 | return vjoy_native.isVJDExists(id); 60 | } 61 | 62 | /** 63 | * Check the status of a vJoy device 64 | * @param {Number} id 65 | * @returns {String} 66 | */ 67 | static status(id) { 68 | let status = vjoy_native.GetVJDStatus(id); 69 | 70 | switch(status) { 71 | case "VJD_STAT_BUSY": 72 | return "busy"; 73 | case "VJD_STAT_MISS": 74 | return "missing"; 75 | case "VJD_STAT_FREE": 76 | return "free"; 77 | case "VJD_STAT_OWN": 78 | return "acquired"; 79 | case "VJD_STAT_UNKN": 80 | default: 81 | return "unknown"; 82 | } 83 | } 84 | 85 | /** 86 | * Check if a vJoy device supports Force Feedback 87 | * @param {Number} id 88 | * @returns {Boolean} 89 | */ 90 | static ffbSupported(id) { 91 | return vjoy_native.IsDeviceFfb(id); 92 | } 93 | 94 | /** 95 | * Get an object containing information about the supported FFB effect types 96 | * @param {Number} id 97 | * @returns {Object} 98 | */ 99 | static getFFBEffects(id) { 100 | return vjoy_native.getDeviceFfbEffects(id); 101 | } 102 | 103 | get id() { 104 | return this._id; 105 | } 106 | 107 | get initialized() { 108 | return this._initialized; 109 | } 110 | 111 | get buttons() { 112 | return Object.freeze(this._buttons); 113 | } 114 | 115 | get discretePOV() { 116 | return Object.freeze(this._discretePOV); 117 | } 118 | 119 | get continuousPOV() { 120 | return Object.freeze(this._continuousPOV); 121 | } 122 | 123 | get axes() { 124 | return Object.freeze(this._axes); 125 | } 126 | 127 | get status() { 128 | return vJoyDevice.status(this.id); 129 | } 130 | 131 | get ffbSupported() { 132 | return vJoyDevice.ffbSupported(this.id); 133 | } 134 | 135 | get ffbEffects() { 136 | return vJoyDevice.getFFBEffects(this.id); 137 | } 138 | 139 | /** 140 | * Initializes the device by checking its status and acquriring it if possible. 141 | * Returns true if the initialization was successful. 142 | * @returns {Boolean} 143 | */ 144 | initialize() { 145 | let status = this.status; 146 | 147 | if (status == "busy" || status == "missing" || status == "unknown") { 148 | return false; 149 | } 150 | 151 | if (status == "free") { 152 | let result = vjoy_native.AcquireVJD(this.id); 153 | 154 | this._initialized = result; 155 | return result; 156 | } else if (status == "acquired") { 157 | this._initialized = true; 158 | return true; 159 | } 160 | } 161 | 162 | /** 163 | * Query the driver for configured axes, buttons and POV switches. 164 | * This also resets all button and POV values 165 | */ 166 | updateInputs() { 167 | if (!this.initialized) throw new Error("Device is not initialized"); 168 | 169 | vjoy_native.ResetVJD(this.id); 170 | 171 | let buttonCount = vjoy_native.GetVJDButtonNumber(this.id); 172 | let discPovCount = vjoy_native.GetVJDDiscPovNumber(this.id); 173 | let contPovCount = vjoy_native.GetVJDContPovNumber(this.id); 174 | 175 | let buttonCountBefore = Object.keys(this._buttons).length; 176 | let discPovCountBefore = Object.keys(this._discretePOV).length; 177 | let contPovCountBefore = Object.keys(this._continuousPOV).length; 178 | 179 | // delete additional buttons 180 | if (buttonCountBefore > buttonCount) { 181 | for(let buttonId = buttonCount + 1; buttonId <= buttonCountBefore; buttonId++) { 182 | delete this._buttons[buttonId]; 183 | } 184 | } 185 | 186 | // create new buttons 187 | for(let buttonId = 1; buttonId <= buttonCount; buttonId++) { 188 | if (this._buttons[buttonId] == undefined) { 189 | this._buttons[buttonId] = new vJoyButton(this, buttonId); 190 | } else { 191 | this._buttons[buttonId]._reset(); 192 | } 193 | } 194 | 195 | // delete additional discrete POV switches 196 | if (discPovCountBefore > discPovCount) { 197 | for(let i = discPovCount + 1; i <= discPovCountBefore; i++) { 198 | delete this._discretePOV[i]; 199 | } 200 | } 201 | 202 | // create new discrete POV switches 203 | for(let povId = 1; povId <= discPovCount; povId++) { 204 | if (this._discretePOV[povId] == undefined) { 205 | this._discretePOV[povId] = new vJoyDiscretePOV(this, povId); 206 | } else { 207 | this._discretePOV[povId]._reset(); 208 | } 209 | } 210 | 211 | // delete additional continuous POV switches 212 | if (contPovCountBefore > contPovCount) { 213 | for(let i = contPovCount + 1; i <= contPovCountBefore; i++) { 214 | delete this._continuousPOV[i]; 215 | } 216 | } 217 | 218 | // create new continuous POV switches 219 | for(let povId = 1; povId <= contPovCount; povId++) { 220 | if (this._continuousPOV[povId] == undefined) { 221 | this._continuousPOV[povId] = new vJoyContinuousPOV(this, povId); 222 | } else { 223 | this._continuousPOV[povId]._reset(); 224 | } 225 | } 226 | 227 | // check which axes exist and update the corresponding object 228 | for(let axisId of vJoy.axes) { 229 | let exists = vjoy_native.GetVJDAxisExists(this.id, axisId); 230 | 231 | if (exists && this._axes[axisId] == null) { 232 | this._axes[axisId] = new vJoyAxis(this, axisId); 233 | } else if (!exists && this._axes[axisId] != null) { 234 | delete this._axes[axisId]; 235 | } 236 | } 237 | } 238 | 239 | /** 240 | * Sets all buttons to not pressed 241 | * @returns {Boolean} 242 | */ 243 | resetButtons() { 244 | let result = vjoy_native.ResetButtons(this.id); 245 | 246 | if (result) { 247 | for(let buttonId in this.buttons) { 248 | this.buttons[buttonId]._reset(); 249 | } 250 | } 251 | 252 | return result; 253 | } 254 | 255 | /** 256 | * Relinquishes the device. Do not forget to relinquish a device if you want other feeders to access it. 257 | */ 258 | free() { 259 | this._initialized = false; 260 | return vjoy_native.RelinquishVJD(this.id); 261 | } 262 | 263 | /** 264 | * Enables the emission of Force Feedback events 265 | */ 266 | enableFFBEvents() { 267 | FFBManager.register(this.id, evt => this.emit("ffb", evt)); 268 | } 269 | 270 | /** 271 | * Disables the emission of Force Feedback events 272 | */ 273 | disableFFBEvents() { 274 | FFBManager.unregister(this.id); 275 | } 276 | } 277 | 278 | module.exports = vJoyDevice; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vJoy Bindings for Node.js 2 | 3 | **ATTENTION**: The module is built against the SDK shipped with a [fork of the original vJoy](https://github.com/njz3/vJoy). 4 | The newer SDK should be backwards compatible, but you will always see an error message if you're running the old official version 2.1.9. 5 | You can disable this warning by setting the environment variable `DISABLE_VJOY_VERSION_CHECK` to any value. 6 | 7 | **NOTE**: If you want to simulate an XInput device (e.g. an Xbox controller), vJoy is probably not right the right solution for you, since it creates DirectInput controllers, which are incompatible with most modern games. 8 | I suggest you take a look at [ViGEmBus](https://github.com/ViGEm/ViGEmBus) and the accompanying [node-ViGEmClient](https://github.com/jangxx/node-ViGEmClient) library instead, which can be used to create virtual Xinput devices. 9 | 10 | # Installation 11 | 12 | ```bash 13 | npm install vjoy 14 | ``` 15 | 16 | Since the vJoy driver only works on Windows, this module can also only be installed on Windows. 17 | If you have problems building the native code parts, try installing the Windows Build Tools by running 18 | 19 | ```bash 20 | npm install -g windows-build-tools 21 | ``` 22 | 23 | # Usage 24 | 25 | A minimal example of a feeder application looks like this: 26 | 27 | ```javascript 28 | const { vJoy, vJoyDevice } = require('vjoy'); 29 | 30 | if (!vJoy.isEnabled()) { 31 | console.log("vJoy is not enabled."); 32 | process.exit(); 33 | } 34 | 35 | let device = vJoyDevice.create(1); 36 | 37 | device.buttons[1].set(true); // press the first button 38 | 39 | device.axes.X.set(10000); // set the X axis to some value 40 | 41 | device.discretePOV[1].set(0); // set the first POV switch to point upwards 42 | ``` 43 | 44 | More examples can be found in the _examples/_ directory. 45 | 46 | # Methods 47 | 48 | ## vJoy 49 | 50 | This object is a collection of several functions, which are more or less exported 1:1 from the SDK. 51 | 52 | **vJoy.isEnabled**() 53 | Returns a boolean indicating, whether or not the vJoy driver is enabled. 54 | 55 | **vJoy.version**() 56 | Returns the numerical version of the vJoy driver. 57 | 58 | **vJoy.productString**() 59 | Returns a string describing the vJoy driver. 60 | 61 | **vJoy.manufacturerString**() 62 | Returns a string describing the manufacturer of the vJoy driver. 63 | 64 | **vJoy.serialString**() 65 | Returns the version number as a String. 66 | 67 | **vJoy.driverMatch**() 68 | Returns an object of the form `{ matches: true, dll_version: 536, drv_version: 536 }`, which contains information about the versions of the SDK and the installed driver. 69 | 70 | **vJoy.maxDevices**() 71 | Returns the maximum number of devices. 72 | 73 | **vJoy.existingDevices**() 74 | Returns the current number of enabled devices. 75 | 76 | **vJoy.registerRemovalCB**(callback) 77 | Registers a function to be called every time a virtual controller is added or removed. 78 | Will be called with `{ removed: bool, first: bool }`. 79 | The meaning of the parameters is explained in the SDK README: 80 | - When the removal process begins, `removed = true` and `first = true` 81 | - When the removal process is finished, `removed = true` and `first = false` 82 | - When the arrival process begins, `removed = false` and `first = true` 83 | - When the arrival process is finished, `removed = false` and `first = false` 84 | 85 | Note: You can only register a single callback. Subsequent calls overwrite the previously set callbacks. 86 | 87 | **vJoy.axes** 88 | A static array of the names of the axes. Contains: `[ "X", "Y", "Z", "Rx", "Ry", "Rz", "Slider0", "Slider1", "Wheel", "POV" ]` 89 | 90 | ## vJoyDevice 91 | 92 | This is the class abstracting the vJoy SDK into an easy-to-use library. 93 | It is recommended to not instantiate this class directly and use `vJoyDevice.create(deviceId)`, which takes care of all the initialization, instead. 94 | 95 | **vJoyDevice.create**(id) 96 | Creates and initializes a new `vJoyDevice`. Returns the new device if the initialization was successful and `null` otherwise. 97 | 98 | **vJoyDevice.exists**(id) 99 | Checks if the device with the specified id exists. 100 | 101 | **vJoyDevice.status**(id) 102 | Queries the driver for the status of the device. Returns one of the following values: 103 | ``` 104 | "busy": The device is owned by another feeder and can not be acquired at this time. 105 | "missing": The vJoy device is missing. 106 | "free": The device is free and can be aquired by running initialize(). 107 | "acquired": The device is already acquired by the current feeder. 108 | "unknown": Something went wrong. 109 | ``` 110 | 111 | **vJoyDevice.ffbSupported**(id) 112 | Checks if the device with the specified id supports Force Feedback. 113 | 114 | **vJoyDevice.getFFBEffects**(id) 115 | Returns an object listing support for the various effect types. 116 | 117 | **constructor**(id) 118 | Creates a new device instance with the supplied id. 119 | 120 | *get* **id** 121 | 122 | *get* **initialized** 123 | 124 | *get* **status** 125 | Contains the status of the device. 126 | 127 | *get* **buttons** 128 | An object mapping button ids to `vJoyButton` objects. Contains all the currently known buttons of the device. 129 | 130 | *get* **discretePOV** 131 | An object mapping POV ids to `vJoyDiscretePOV` objects. Contains all the currently known discrete POV switches of the device. 132 | 133 | *get* **continuousPOV** 134 | An object mapping POV ids to `vJoyContinuousPOV` objects. Contains all the currently known continuous POV switches of the device. 135 | 136 | *get* **axes** 137 | An object mapping axis ids (from `vJoy.axes`) to `vJoyAxis` objects, or `null` if the axis is not configured. 138 | 139 | *get* **ffbSupported** 140 | Contains the Force Feedback support status. 141 | 142 | *get* **ffbEffects** 143 | Contains an object listing support for the various effect types. 144 | 145 | **initialize**() 146 | Tries to acquire the device. Updates the internal initialized status and returns `true` if the device was acquired. 147 | 148 | **updateInputs**() 149 | Query the driver for the number of buttons, pov switches and enabled axes and update the `buttons`, `discretePOV`, `continuousPOV` and `axes` properties. 150 | This also resets all buttons and pov switches to an unpressed state. 151 | 152 | **resetButtons**() 153 | Resets all buttons to an unpressed state. 154 | 155 | **free**() 156 | Relinquishes the device. **THIS NEEDS TO BE CALLED WHEN YOU DON'T NEED THE DEVICE ANYMORE, OTHERWISE NO OTHER PROCESS CAN ACQUIRE IT**. 157 | 158 | **enableFFBEvents**() 159 | Enables the emission of Force Feedback events. 160 | Note: After this method has been called on any device, the Node process will not exit on its own anymore, since a event listener has been registered with the driver. 161 | 162 | **disableFFBEvents**() 163 | Disabled the emission of Force Feedback events. 164 | This will not unregister the event listener from the driver however (since that is impossible), so you still need to quit your program explicitly. 165 | 166 | ### Events 167 | 168 | Event "**ffb**" 169 | This is event will only be emitted after `enableFFBEvents()` has been called on a device. 170 | The emitted object has the format 171 | ``` 172 | { 173 | type: String 174 | type_n: Number, 175 | effect: Object, 176 | ebi: Number, 177 | gain: Number, 178 | control: String, 179 | control_n: Number 180 | } 181 | ``` 182 | Each of the values can also be `null`, in case the respective value was not found in the effect packet sent by the driver. 183 | Every field ending in `_n` is just the numerical representation of a string field with the same name. 184 | A complete overview over the different effect types and the data contained within them is outside the scope of this README and can be found in the documentation of the vJoy SDK. 185 | 186 | ## vJoyButton, vJoyDiscretePOV, vJoyContinuousPOV, vJoyAxis 187 | 188 | These are the classes abstracting the different types of input on a device and they are all very similar. 189 | You can not instantiate these youself, instead you get them from the `buttons`, `discretePOV`, `continuousPOV` and `axes` properties on a `vJoyDevice`. 190 | 191 | *get* **id** 192 | 193 | *get* **value** 194 | Contains the current value of the input. 195 | 196 | **set**(value) 197 | Sets the input to the specified value and returns a boolean indicating if the operation was successful. 198 | The values and their meanings differ for all the input types. 199 | 200 | - vJoyButton: The value is a boolean indicating if the button is pressed. 201 | - vJoyDiscretePOV: The value is an integer between -1 and 3 with the following meaning: _-1 = neutral, 0 = up, 1 = left, 2 = down, 3 = right_ 202 | - vJoyContinuousPOV: The value is an integer between -1 and 35999, where _-1 = neutral, 0-35999 = 1/100 degree steps with 0 = up_ 203 | - vJoyAxis: The value is an integer between 1 and 32769. 204 | 205 | # Attribution 206 | 207 | The vJoy SDK is licensed under the MIT license. 208 | 209 | ``` 210 | MIT License 211 | 212 | Copyright (c) 2017 Shaul Eizikovich 213 | 214 | Permission is hereby granted, free of charge, to any person obtaining a copy 215 | of this software and associated documentation files (the "Software"), to deal 216 | in the Software without restriction, including without limitation the rights 217 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 218 | copies of the Software, and to permit persons to whom the Software is 219 | furnished to do so, subject to the following conditions: 220 | 221 | The above copyright notice and this permission notice shall be included in all 222 | copies or substantial portions of the Software. 223 | 224 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 225 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 226 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 227 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 228 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 229 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 230 | SOFTWARE. 231 | ``` -------------------------------------------------------------------------------- /src/ffb.cpp: -------------------------------------------------------------------------------- 1 | #include "ffb.h" 2 | 3 | Napi::ThreadSafeFunction ffbCB_function = nullptr; 4 | 5 | const std::map ffbpTypeStrings = { 6 | { PT_EFFREP, "PT_EFFREP" }, 7 | { PT_ENVREP, "PT_ENVREP" }, 8 | { PT_CONDREP, "PT_CONDREP" }, 9 | { PT_PRIDREP, "PT_PRIDREP" }, 10 | { PT_CONSTREP, "PT_CONSTREP" }, 11 | { PT_RAMPREP, "PT_RAMPREP" }, 12 | { PT_CSTMREP, "PT_CSTMREP" }, 13 | { PT_SMPLREP, "PT_SMPLREP" }, 14 | { PT_EFOPREP, "PT_EFOPREP" }, 15 | { PT_BLKFRREP, "PT_BLKFRREP" }, 16 | { PT_CTRLREP, "PT_CTRLREP" }, 17 | { PT_GAINREP, "PT_GAINREP" }, 18 | { PT_SETCREP, "PT_SETCREP" }, 19 | { PT_NEWEFREP, "PT_NEWEFREP", }, 20 | { PT_BLKLDREP, "PT_BLKLDREP", }, 21 | { PT_POOLREP, "PT_POOLREP" } 22 | }; 23 | 24 | const std::map ffbOpStrings = { 25 | { EFF_START, "EFF_START" }, 26 | { EFF_SOLO, "EFF_SOLO" }, 27 | { EFF_STOP, "EFF_STOP" } 28 | }; 29 | 30 | const std::map ffbCtrlStrings = { 31 | { CTRL_ENACT, "CTRL_ENACT" }, 32 | { CTRL_DISACT, "CTRL_DISACT" }, 33 | { CTRL_STOPALL, "CTRL_STOPALL" }, 34 | { CTRL_DEVRST, "CTRL_DEVRST" }, 35 | { CTRL_DEVPAUSE, "CTRL_DEVPAUSE" }, 36 | { CTRL_DEVCONT, "CTRL_DEVCONT" }, 37 | }; 38 | 39 | // Napi::Boolean wrap_FfbStart(const Napi::CallbackInfo& info) { 40 | // Napi::Env env = info.Env(); 41 | 42 | // UINT rID = info[0].As().Uint32Value(); 43 | 44 | // bool success = FfbStart(rID); 45 | 46 | // return Napi::Boolean::New(env, success); 47 | // } 48 | 49 | // void wrap_FfbStop(const Napi::CallbackInfo& info) { 50 | // Napi::Env env = info.Env(); 51 | 52 | // UINT rID = info[0].As().Uint32Value(); 53 | 54 | // FfbStop(rID); 55 | // } 56 | 57 | Napi::Value wrap_Ffb_h_Eff_Report(Napi::Env env, const FFB_DATA* packet) { 58 | FFB_EFF_REPORT effect; 59 | DWORD result = Ffb_h_Eff_Report(packet, &effect); 60 | 61 | if (result != ERROR_SUCCESS) { 62 | return env.Null(); 63 | } 64 | 65 | Napi::Object ret = Napi::Object::New(env); 66 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 67 | ret.Set("effectType", Napi::Number::New(env, (double)effect.EffectType)); 68 | ret.Set("duration", Napi::Number::New(env, (double)effect.Duration)); 69 | ret.Set("trigerRpt", Napi::Number::New(env, (double)effect.TrigerRpt)); 70 | ret.Set("samplePrd", Napi::Number::New(env, (double)effect.SamplePrd)); 71 | ret.Set("gain", Napi::Number::New(env, (double)effect.Gain)); 72 | ret.Set("trigerBtn", Napi::Number::New(env, (double)effect.TrigerBtn)); 73 | ret.Set("polar", Napi::Boolean::New(env, effect.Polar == TRUE)); 74 | 75 | if (effect.Polar == TRUE) { 76 | ret.Set("direction", Napi::Number::New(env, (double)effect.Direction)); 77 | } else { 78 | ret.Set("dirX", Napi::Number::New(env, (double)effect.DirX)); 79 | ret.Set("dirY", Napi::Number::New(env, (double)effect.DirY)); 80 | } 81 | 82 | return ret; 83 | } 84 | 85 | Napi::Value wrap_Ffb_h_Eff_Ramp(Napi::Env env, const FFB_DATA* packet) { 86 | FFB_EFF_RAMP effect; 87 | DWORD result = Ffb_h_Eff_Ramp(packet, &effect); 88 | 89 | if (result != ERROR_SUCCESS) { 90 | return env.Null(); 91 | } 92 | 93 | Napi::Object ret = Napi::Object::New(env); 94 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 95 | ret.Set("start", Napi::Number::New(env, effect.Start)); 96 | ret.Set("end", Napi::Number::New(env, effect.End)); 97 | 98 | return ret; 99 | } 100 | 101 | Napi::Value wrap_Ffb_h_EffOp(Napi::Env env, const FFB_DATA* packet) { 102 | FFB_EFF_OP effect; 103 | DWORD result = Ffb_h_EffOp(packet, &effect); 104 | 105 | if (result != ERROR_SUCCESS) { 106 | return env.Null(); 107 | } 108 | 109 | Napi::Object ret = Napi::Object::New(env); 110 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 111 | ret.Set("effectOp", Napi::String::New(env, ffbOpStrings.at(effect.EffectOp))); 112 | ret.Set("effectOp_n", Napi::Number::New(env, (double)effect.EffectOp)); 113 | ret.Set("loopCount", Napi::Number::New(env, effect.LoopCount)); 114 | 115 | return ret; 116 | } 117 | 118 | Napi::Value wrap_Ffb_h_Eff_Period(Napi::Env env, const FFB_DATA* packet) { 119 | FFB_EFF_PERIOD effect; 120 | DWORD result = Ffb_h_Eff_Period(packet, &effect); 121 | 122 | if (result != ERROR_SUCCESS) { 123 | return env.Null(); 124 | } 125 | 126 | Napi::Object ret = Napi::Object::New(env); 127 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 128 | ret.Set("magnitude", Napi::Number::New(env, effect.Magnitude)); 129 | ret.Set("offset", Napi::Number::New(env, effect.Offset)); 130 | ret.Set("phase", Napi::Number::New(env, effect.Phase)); 131 | ret.Set("period", Napi::Number::New(env, effect.Period)); 132 | 133 | return ret; 134 | } 135 | 136 | Napi::Value wrap_Ffb_h_Eff_Cond(Napi::Env env, const FFB_DATA* packet) { 137 | FFB_EFF_COND effect; 138 | DWORD result = Ffb_h_Eff_Cond(packet, &effect); 139 | 140 | if (result != ERROR_SUCCESS) { 141 | return env.Null(); 142 | } 143 | 144 | Napi::Object ret = Napi::Object::New(env); 145 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 146 | ret.Set("isY", Napi::Boolean::New(env, effect.isY == TRUE)); 147 | ret.Set("centerPointOffset", Napi::Number::New(env, effect.CenterPointOffset)); 148 | ret.Set("posCoeff", Napi::Number::New(env, effect.PosCoeff)); 149 | ret.Set("negCoeff", Napi::Number::New(env, effect.NegCoeff)); 150 | ret.Set("posSatur", Napi::Number::New(env, effect.PosSatur)); 151 | ret.Set("negSatur", Napi::Number::New(env, effect.NegSatur)); 152 | ret.Set("deadBand", Napi::Number::New(env, effect.DeadBand)); 153 | 154 | return ret; 155 | } 156 | 157 | Napi::Value wrap_Ffb_h_Eff_Envlp(Napi::Env env, const FFB_DATA* packet) { 158 | FFB_EFF_ENVLP effect; 159 | DWORD result = Ffb_h_Eff_Envlp(packet, &effect); 160 | 161 | if (result != ERROR_SUCCESS) { 162 | return env.Null(); 163 | } 164 | 165 | Napi::Object ret = Napi::Object::New(env); 166 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 167 | ret.Set("attackLevel", Napi::Number::New(env, (double)effect.AttackLevel)); 168 | ret.Set("fadeLevel", Napi::Number::New(env, (double)effect.FadeLevel)); 169 | ret.Set("attackTime", Napi::Number::New(env, (double)effect.AttackTime)); 170 | ret.Set("fadeTime", Napi::Number::New(env, (double)effect.FadeTime)); 171 | 172 | return ret; 173 | } 174 | 175 | Napi::Value wrap_Ffb_h_Eff_Constant(Napi::Env env, const FFB_DATA* packet) { 176 | FFB_EFF_CONSTANT effect; 177 | DWORD result = Ffb_h_Eff_Constant(packet, &effect); 178 | 179 | if (result != ERROR_SUCCESS) { 180 | return env.Null(); 181 | } 182 | 183 | Napi::Object ret = Napi::Object::New(env); 184 | ret.Set("effectBlockIndex", Napi::Number::New(env, (double)effect.EffectBlockIndex)); 185 | ret.Set("magnitude", Napi::Number::New(env, effect.Magnitude)); 186 | 187 | return ret; 188 | } 189 | 190 | void FFBcallback(PVOID FfbPacket, PVOID userdata) { 191 | ffbCB_function.NonBlockingCall(FfbPacket, [](Napi::Env env, Napi::Function fn, PVOID packet) { 192 | FFB_DATA* data = (FFB_DATA*)packet; 193 | 194 | Napi::Object evt = Napi::Object::New(env); 195 | 196 | UINT deviceID = 0; 197 | DWORD result = Ffb_h_DeviceID(data, &deviceID); 198 | 199 | FFBPType type; 200 | result = Ffb_h_Type(data, &type); 201 | 202 | if (result == ERROR_SUCCESS) { 203 | evt.Set("type", Napi::String::New(env, ffbpTypeStrings.at(type))); 204 | evt.Set("type_n", Napi::Number::New(env, (double)type)); 205 | 206 | switch (type) { 207 | case PT_EFFREP: 208 | evt.Set("effect", wrap_Ffb_h_Eff_Report(env, data)); 209 | break; 210 | case PT_RAMPREP: 211 | evt.Set("effect", wrap_Ffb_h_Eff_Ramp(env, data)); 212 | break; 213 | case PT_EFOPREP: 214 | evt.Set("effect", wrap_Ffb_h_EffOp(env, data)); 215 | break; 216 | case PT_PRIDREP: 217 | evt.Set("effect", wrap_Ffb_h_Eff_Period(env, data)); 218 | break; 219 | case PT_CONDREP: 220 | evt.Set("effect", wrap_Ffb_h_Eff_Cond(env, data)); 221 | break; 222 | case PT_ENVREP: 223 | evt.Set("effect", wrap_Ffb_h_Eff_Envlp(env, data)); 224 | break; 225 | case PT_CONSTREP: 226 | evt.Set("effect", wrap_Ffb_h_Eff_Constant(env, data)); 227 | break; 228 | default: 229 | evt.Set("effect", env.Null()); 230 | break; 231 | } 232 | } else { 233 | evt.Set("type", env.Null()); 234 | evt.Set("type_n", env.Null()); 235 | evt.Set("effect", env.Null()); 236 | } 237 | 238 | UINT effectBlockIndex = 0; 239 | 240 | #if VER_X_ >= 2 && VER_H_ >= 2 241 | result = Ffb_h_EffectBlockIndex(data, &effectBlockIndex); 242 | #else 243 | result = Ffb_h_EBI(data, &effectBlockIndex); 244 | #endif 245 | 246 | if (result == ERROR_SUCCESS) { 247 | evt.Set("ebi", Napi::Number::New(env, (double)effectBlockIndex)); 248 | } else { 249 | evt.Set("ebi", env.Null()); 250 | } 251 | 252 | BYTE gain = 0; 253 | result = Ffb_h_DevGain(data, &gain); 254 | if (result == ERROR_SUCCESS) { 255 | evt.Set("gain", Napi::Number::New(env, (double)gain)); 256 | } else { 257 | evt.Set("gain", env.Null()); 258 | } 259 | 260 | FFB_CTRL control; 261 | result = Ffb_h_DevCtrl(data, &control); 262 | if (result == ERROR_SUCCESS) { 263 | evt.Set("control", Napi::String::New(env, ffbCtrlStrings.at(control))); 264 | evt.Set("control", Napi::Number::New(env, (double)control)); 265 | } else { 266 | evt.Set("control", env.Null()); 267 | evt.Set("control_n", env.Null()); 268 | } 269 | 270 | // pass data to JS callback 271 | fn.Call({ Napi::Number::New(env, (double)deviceID), evt }); 272 | 273 | // lets hope we don't have to clean up the other data 274 | }); 275 | } 276 | 277 | void wrap_FfbRegisterGenCB(const Napi::CallbackInfo& info) { 278 | Napi::Env env = info.Env(); 279 | 280 | if (!info[0].IsFunction()) { 281 | Napi::Error::New(env, "The first parameter needs to be a function").ThrowAsJavaScriptException(); 282 | return; 283 | } 284 | 285 | Napi::Function callback = info[0].As(); 286 | ffbCB_function = Napi::ThreadSafeFunction::New(env, callback, "forcefeedbackCB", 0, 1); 287 | 288 | FfbRegisterGenCB(&FFBcallback, nullptr); 289 | } 290 | 291 | Napi::Boolean wrap_IsDeviceFfb(const Napi::CallbackInfo& info) { 292 | Napi::Env env = info.Env(); 293 | 294 | UINT rID = info[0].As().Uint32Value(); 295 | 296 | bool ffbSupported = IsDeviceFfb(rID); 297 | 298 | return Napi::Boolean::New(env, ffbSupported); 299 | } 300 | 301 | Napi::Object getDeviceFfbEffects(const Napi::CallbackInfo& info) { 302 | Napi::Env env = info.Env(); 303 | 304 | UINT rID = info[0].As().Uint32Value(); 305 | 306 | Napi::Object ret = Napi::Object::New(env); 307 | 308 | ret.Set("constant", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_CONST) == TRUE)); 309 | ret.Set("ramp", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_RAMP) == TRUE)); 310 | ret.Set("square", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_SQUR) == TRUE)); 311 | ret.Set("sine", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_SINE) == TRUE)); 312 | ret.Set("triangle", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_TRNG) == TRUE)); 313 | ret.Set("sawtooth_up", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_STUP) == TRUE)); 314 | ret.Set("sawtooth_down", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_STDN) == TRUE)); 315 | ret.Set("spring", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_SPRNG) == TRUE)); 316 | ret.Set("damper", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_DMPR) == TRUE)); 317 | ret.Set("inertia", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_INRT) == TRUE)); 318 | ret.Set("friction", Napi::Boolean::New(env, IsDeviceFfbEffect(rID, HID_USAGE_FRIC) == TRUE)); 319 | 320 | return ret; 321 | } -------------------------------------------------------------------------------- /include/public.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Shaul Eizikovich. All rights reserved. 4 | 5 | THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 6 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 7 | IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR 8 | PURPOSE. 9 | 10 | Module Name: 11 | 12 | public.h 13 | 14 | Abstract: 15 | 16 | Public header file for the vJoy project 17 | Developpers that need to interface with vJoy need to include this file 18 | 19 | Author: 20 | 21 | 22 | Environment: 23 | 24 | kernel mode and User mode 25 | 26 | Notes: 27 | 28 | 29 | Revision History: 30 | 31 | 32 | --*/ 33 | #ifndef _PUBLIC_H 34 | #define _PUBLIC_H 35 | #pragma once 36 | 37 | // Compilation directives 38 | #define PPJOY_MODE 39 | #undef PPJOY_MODE // Comment-out for compatibility mode 40 | 41 | #ifdef PPJOY_MODE 42 | #include "PPJIoctl.h" 43 | #endif 44 | 45 | #include // Definitions for controlling GUID initialization 46 | 47 | // Sideband comunication with vJoy Device 48 | //{781EF630-72B2-11d2-B852-00C04FAD5101} 49 | DEFINE_GUID(GUID_DEVINTERFACE_VJOY, 0x781EF630, 0x72B2, 0x11d2, 0xB8, 0x52, 0x00, 0xC0, 0x4F, 0xAD, 0x51, 0x01); 50 | 51 | // 52 | // Usage example: 53 | // CreateFile(TEXT("\\\\.\\vJoy"), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); 54 | #ifdef PPJOY_MODE 55 | #define DEVICENAME_STRING "PPJoyIOCTL1" 56 | #else 57 | #define DEVICENAME_STRING "vJoy" 58 | #endif 59 | #define NTDEVICE_NAME_STRING "\\Device\\"DEVICENAME_STRING 60 | #define SYMBOLIC_NAME_STRING "\\DosDevices\\"DEVICENAME_STRING 61 | #define DOS_FILE_NAME "\\\\.\\"DEVICENAME_STRING 62 | #define VJOY_INTERFACE L"Device_" 63 | 64 | // Use auto-generated version file from inc/ 65 | #include "gen-versioninfo.h" 66 | // This is a hardcoded version, not to be confused with whole {installer+libs+SDK} that 67 | // use getversion to retrieve a 'git describe' (tag) version. 68 | // Version parts as a serie of digits (from higher (X) significant number to lower (L)) 69 | // Will be taken from CreateVersion.exe in gen-versioninfo.h 70 | #ifndef VER_X_ 71 | #define VER_X_ 2 // Must be within 0..0XF 72 | #define VER_H_ 2 // Must be within 0..0XF 73 | #define VER_M_ 1 // Must be within 0..0XF 74 | #define VER_L_ 0 // Must be within 0..0XF 75 | #endif 76 | 77 | 78 | // Use to select which level of API is being used by the driver : 1 2 or 3 79 | // See JOYSTICK_POSITION, JOYSTICK_POSITION_V2, JOYSTICK_POSITION_V3 80 | #define USE_JOYSTICK_API_VERSION 3 81 | 82 | #ifndef USE_JOYSTICK_API_VERSION 83 | #error "Must select an API version!" 84 | #endif // !USE_JOYSTICK_API_VERSION 85 | 86 | 87 | // Select whether driver has support for FFB 88 | #define VJOY_HAS_FFB 89 | // For FFB, Look for Registrys Keys to cleanup: 90 | // HKEY_CURRENT_USER\\SYSTEM\\CurrentControlSet\\Control\\MediaProperties\\PrivateProperties\\Joystick\\OEM\\VID_1234& PID_0FFB" 91 | // HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\MediaProperties\PrivateProperties\Joystick\OEM\VID_1234&PID_0FFB 92 | // HKEY_LOCAL_MACHINE\SYSTEM\Setup\Upgrade\PnP\CurrentControlSet\Control\DeviceMigration\Devices\HID\HIDCLASS\* 93 | // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\PnpResources\Registry\HKLM\SYSTEM\CurrentControlSet\Control\MediaProperties\PrivateProperties\Joystick\OEM 94 | // HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Setup\PnpResources\Registry\HKLM\SYSTEM\CurrentControlSet\Control\MediaProperties\PrivateProperties\Joystick\OEM\VID_1234&PID_1FFB 95 | 96 | // For FFB: when bellow flag is enabled, a first Effect Block Index is always allocated, 97 | // even if there is no feeder. 98 | // This is to avoid some games being locked if a feeder with FFB was not started. 99 | // This also enables RAM Pool emulation. 100 | #define FIRST_EID_PREALLOCATED 101 | 102 | 103 | #define STRINGIFY_1(x) #x 104 | #define STRINGIFY(x) STRINGIFY_1(x) 105 | #define PASTE(x, y) x##y 106 | #define MAKEWIDE(x) PASTE(L,x) 107 | 108 | // Device Attributes 109 | // Vendor ID 110 | #define VENDOR_N_ID 0x1234 111 | // Product ID changes if FFB is enabled or not (so we will have 2 different products and drivers) 112 | #ifdef VJOY_HAS_FFB 113 | // Product ID: FFB with registry keys in INF file 114 | #define PRODUCT_N_ID 0xBEAD 115 | // Product ID: FFB without registry keys in INF file 116 | //#define PRODUCT_N_ID 0x1FFB 117 | #else 118 | #define PRODUCT_N_ID 0x0FFB 119 | #endif 120 | 121 | // Complete driver version on 16bits for HID and driver: v0.0.0.0 (4 nibbles) 122 | // To avoid bugfix numbering to be reported in vjoy.inx file each time we have a new driver, 123 | // just leave the 4th number off. 124 | // /!\ This number must matched what is written in the vjoy.inx file (root\VID_1234&PID_BEAD&REV_0XHM) 125 | #define VERSION_N (0x100*VER_X_ + 0x10*VER_H_ + VER_M_) 126 | // In case we want to move to 4digits, use following and change vjoy.inx accordingly 127 | //#define VERSION_N (0x1000*VER_X_ + 0x100*VER_H_ + 0*010*VER_M_ + VER_L_) 128 | 129 | // Device Strings 130 | // 131 | #define VENDOR_STR_ID L"Shaul Eizikovich" 132 | #define PRODUCT_STR_ID L"vJoy - Virtual Joystick" 133 | #define SERIALNUMBER_STR MAKEWIDE(STRINGIFY(VER_X_)) L"." MAKEWIDE(STRINGIFY(VER_H_)) L"." MAKEWIDE(STRINGIFY(VER_M_)) L"." MAKEWIDE(STRINGIFY(VER_L_)) 134 | 135 | // Function codes; 136 | //#define LOAD_POSITIONS 0x910 137 | //#define GETATTRIB 0x911 138 | // #define GET_FFB_DATA 0x00222912 // METHOD_OUT_DIRECT + FILE_DEVICE_UNKNOWN + FILE_ANY_ACCESS 139 | //#define SET_FFB_STAT 0x913 // METHOD_NEITHER 140 | //#define GET_FFB_STAT 0x916 141 | 142 | #define F_LOAD_POSITIONS 0x910 143 | #define F_GETATTRIB 0x911 144 | #define F_GET_FFB_DATA 0x912 145 | #define F_SET_FFB_STAT 0x913 146 | #define F_GET_FFB_PID_DATA 0x914 147 | #define F_SET_FFB_PID_DATA 0x915 148 | #define F_GET_FFB_STAT 0x916 149 | #define F_GET_DEV_INFO 0x917 150 | #define F_IS_DRV_FFB_CAP 0x918 151 | #define F_IS_DRV_FFB_EN 0x919 152 | #define F_GET_DRV_DEV_MAX 0x91A 153 | #define F_GET_DRV_DEV_EN 0x91B 154 | #define F_IS_DEV_FFB_START 0x91C 155 | #define F_GET_DEV_STAT 0x91D 156 | #define F_GET_DRV_INFO 0x91E 157 | #define F_RESET_DEV 0x91F 158 | #define F_GET_POSITIONS 0x920 159 | 160 | // IO Device Control codes; 161 | #define IOCTL_VJOY_GET_ATTRIB CTL_CODE (FILE_DEVICE_UNKNOWN, GETATTRIB, METHOD_BUFFERED, FILE_WRITE_ACCESS) 162 | #define LOAD_POSITIONS CTL_CODE (FILE_DEVICE_UNKNOWN, F_LOAD_POSITIONS, METHOD_BUFFERED, FILE_WRITE_ACCESS) 163 | #define GET_POSITIONS CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_POSITIONS, METHOD_BUFFERED, FILE_READ_ACCESS) 164 | #define GET_FFB_DATA CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_FFB_DATA, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) 165 | #define GET_FFB_PID_DATA CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_FFB_PID_DATA, METHOD_BUFFERED, FILE_ANY_ACCESS) 166 | #define SET_FFB_PID_DATA CTL_CODE (FILE_DEVICE_UNKNOWN, F_SET_FFB_PID_DATA, METHOD_BUFFERED, FILE_ANY_ACCESS) 167 | #define SET_FFB_STAT CTL_CODE (FILE_DEVICE_UNKNOWN, F_SET_FFB_STAT, METHOD_NEITHER, FILE_ANY_ACCESS) 168 | #define GET_FFB_STAT CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_FFB_STAT, METHOD_BUFFERED, FILE_ANY_ACCESS) 169 | #define GET_DEV_INFO CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_DEV_INFO, METHOD_BUFFERED, FILE_ANY_ACCESS) 170 | #define IS_DRV_FFB_CAP CTL_CODE (FILE_DEVICE_UNKNOWN, F_IS_DRV_FFB_CAP, METHOD_BUFFERED, FILE_ANY_ACCESS) 171 | #define IS_DRV_FFB_EN CTL_CODE (FILE_DEVICE_UNKNOWN, F_IS_DRV_FFB_EN, METHOD_BUFFERED, FILE_ANY_ACCESS) 172 | #define GET_DRV_DEV_MAX CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_DRV_DEV_MAX, METHOD_BUFFERED, FILE_ANY_ACCESS) 173 | #define GET_DRV_DEV_EN CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_DRV_DEV_EN, METHOD_BUFFERED, FILE_ANY_ACCESS) 174 | #define IS_DEV_FFB_START CTL_CODE (FILE_DEVICE_UNKNOWN, F_IS_DEV_FFB_START, METHOD_BUFFERED, FILE_ANY_ACCESS) 175 | #define GET_DEV_STAT CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_DEV_STAT, METHOD_BUFFERED, FILE_ANY_ACCESS) 176 | #define GET_DRV_INFO CTL_CODE (FILE_DEVICE_UNKNOWN, F_GET_DRV_INFO, METHOD_BUFFERED, FILE_ANY_ACCESS) 177 | #define RESET_DEV CTL_CODE (FILE_DEVICE_UNKNOWN, F_RESET_DEV, METHOD_BUFFERED, FILE_WRITE_ACCESS) 178 | 179 | #ifndef __HIDPORT_H__ 180 | // Copied from hidport.h 181 | #define IOCTL_HID_SET_FEATURE 0xB0191 182 | #define IOCTL_HID_WRITE_REPORT 0xB000F 183 | 184 | 185 | 186 | typedef struct _HID_DEVICE_ATTRIBUTES { 187 | 188 | ULONG Size; 189 | // 190 | // sizeof (struct _HID_DEVICE_ATTRIBUTES) 191 | // 192 | 193 | // 194 | // Vendor ids of this hid device 195 | // 196 | USHORT VendorID; 197 | USHORT ProductID; 198 | USHORT VersionNumber; 199 | USHORT Reserved[11]; 200 | 201 | } HID_DEVICE_ATTRIBUTES, * PHID_DEVICE_ATTRIBUTES; 202 | #endif 203 | 204 | // Device Type 205 | //enum DevType { vJoy, vXbox }; 206 | 207 | // Error levels for status report 208 | enum ERRLEVEL { INFO, WARN, ERR, FATAL, APP }; 209 | // Status report function prototype 210 | #ifdef WINAPI 211 | typedef BOOL(WINAPI* StatusMessageFunc)(void* output, TCHAR* buffer, enum ERRLEVEL level); 212 | #endif 213 | 214 | /////////////////////////////////////////////////////////////// 215 | 216 | /////////////////////// Joystick Position /////////////////////// 217 | // 218 | // This structure holds data that is passed to the device from 219 | // an external application such as SmartPropoPlus. 220 | // 221 | // Usage example: 222 | // JOYSTICK_POSITION iReport; 223 | // : 224 | // DeviceIoControl (hDevice, 100, &iReport, sizeof(HID_INPUT_REPORT), NULL, 0, &bytes, NULL) 225 | 226 | 227 | #if USE_JOYSTICK_API_VERSION == 1 228 | 229 | typedef struct _JOYSTICK_POSITION 230 | { 231 | BYTE bDevice; // Index of device. 1-based. 232 | LONG wThrottle; 233 | LONG wRudder; 234 | LONG wAileron; 235 | LONG wAxisX; 236 | LONG wAxisY; 237 | LONG wAxisZ; 238 | LONG wAxisXRot; 239 | LONG wAxisYRot; 240 | LONG wAxisZRot; 241 | LONG wSlider; 242 | LONG wDial; 243 | LONG wWheel; 244 | LONG wAxisVX; 245 | LONG wAxisVY; 246 | LONG wAxisVZ; 247 | LONG wAxisVBRX; 248 | LONG wAxisVBRY; 249 | LONG wAxisVBRZ; 250 | LONG lButtons; // 32 buttons: 0x00000001 means button1 is pressed, 0x80000000 -> button32 is pressed 251 | DWORD bHats; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 252 | DWORD bHatsEx1; // 16-bit of continuous HAT switch 253 | DWORD bHatsEx2; // 16-bit of continuous HAT switch 254 | DWORD bHatsEx3; // 16-bit of continuous HAT switch 255 | } JOYSTICK_POSITION, * PJOYSTICK_POSITION; 256 | 257 | #define VJOY_MAX_N_DEVICES 16 // Maximum number of vJoy devices 258 | #define VJOY_NUMBER_OF_AXES (8) // Maximum number of axes 259 | #define VJOY_NUMBER_OF_HAT (4) // Maximum number of hats 260 | #define VJOY_NUMBER_OF_BUTTONS (128) // Maximum number of hat 261 | #define VJOY_AXIS_MAX_VALUE (0x7FFF) // Maximum value for an axis 262 | 263 | #elif USE_JOYSTICK_API_VERSION == 2 264 | 265 | // Superset of JOYSTICK_POSITION 266 | // Extension of JOYSTICK_POSITION with Buttons 33-128 appended to the end of the structure. 267 | typedef struct _JOYSTICK_POSITION_V2 268 | { 269 | /// JOYSTICK_POSITION 270 | BYTE bDevice; // Index of device. 1-based. 271 | LONG wThrottle; 272 | LONG wRudder; 273 | LONG wAileron; 274 | LONG wAxisX; 275 | LONG wAxisY; 276 | LONG wAxisZ; 277 | LONG wAxisXRot; 278 | LONG wAxisYRot; 279 | LONG wAxisZRot; 280 | LONG wSlider; 281 | LONG wDial; 282 | LONG wWheel; 283 | LONG wAxisVX; 284 | LONG wAxisVY; 285 | LONG wAxisVZ; 286 | LONG wAxisVBRX; 287 | LONG wAxisVBRY; 288 | LONG wAxisVBRZ; 289 | 290 | LONG lButtons; // 32 buttons: 0x00000001 means button1 is pressed, 0x80000000 -> button32 is pressed 291 | DWORD bHats; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 292 | DWORD bHatsEx1; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 293 | DWORD bHatsEx2; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 294 | DWORD bHatsEx3; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch LONG lButtonsEx1; // Buttons 33-64 295 | 296 | /// JOYSTICK_POSITION_V2 Extenssion 297 | LONG lButtonsEx1; // Buttons 33-64 298 | LONG lButtonsEx2; // Buttons 65-96 299 | LONG lButtonsEx3; // Buttons 97-128 300 | } JOYSTICK_POSITION_V2, * PJOYSTICK_POSITION_V2; 301 | typedef JOYSTICK_POSITION_V2 JOYSTICK_POSITION; 302 | typedef PJOYSTICK_POSITION_V2 PJOYSTICK_POSITION; 303 | 304 | #define VJOY_MAX_N_DEVICES 16 // Maximum number of vJoy devices 305 | #define VJOY_NUMBER_OF_AXES (8) // Maximum number of axes 306 | #define VJOY_NUMBER_OF_HAT (4) // Maximum number of hats 307 | #define VJOY_NUMBER_OF_BUTTONS (128) // Maximum number of hat 308 | #define VJOY_AXIS_MAX_VALUE (0x7FFF) // Maximum value for an axis 309 | 310 | 311 | #elif USE_JOYSTICK_API_VERSION == 3 312 | 313 | // Extension of JOYSTICK_POSITION for more axes 314 | typedef struct _JOYSTICK_POSITION_V3 315 | { 316 | /// JOYSTICK_POSITION 317 | BYTE bDevice; // Index of device. 1-based. 318 | 319 | LONG wThrottle; 320 | LONG wRudder; 321 | LONG wAileron; 322 | 323 | LONG wAxisX; 324 | LONG wAxisY; 325 | LONG wAxisZ; 326 | LONG wAxisXRot; 327 | LONG wAxisYRot; 328 | LONG wAxisZRot; 329 | LONG wSlider; 330 | LONG wDial; 331 | 332 | LONG wWheel; 333 | LONG wAccelerator; 334 | LONG wBrake; 335 | LONG wClutch; 336 | LONG wSteering; 337 | 338 | LONG wAxisVX; 339 | LONG wAxisVY; 340 | 341 | LONG lButtons; // 32 buttons: 0x00000001 means button1 is pressed, 0x80000000 -> button32 is pressed 342 | 343 | DWORD bHats; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 344 | DWORD bHatsEx1; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 345 | DWORD bHatsEx2; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch 346 | DWORD bHatsEx3; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch LONG lButtonsEx1; // Buttons 33-64 347 | 348 | /// JOYSTICK_POSITION_V2 Extenssion 349 | LONG lButtonsEx1; // Buttons 33-64 350 | LONG lButtonsEx2; // Buttons 65-96 351 | LONG lButtonsEx3; // Buttons 97-128 352 | 353 | // JOYSTICK Extension V3: replacing old slots and moving them at the tail 354 | LONG wAxisVZ; 355 | LONG wAxisVBRX; 356 | LONG wAxisVBRY; 357 | LONG wAxisVBRZ; 358 | 359 | } JOYSTICK_POSITION_V3, * PJOYSTICK_POSITION_V3; 360 | typedef JOYSTICK_POSITION_V3 JOYSTICK_POSITION; 361 | typedef PJOYSTICK_POSITION_V3 PJOYSTICK_POSITION; 362 | 363 | #define VJOY_MAX_N_DEVICES (16) // Maximum number of vJoy devices 364 | #define VJOY_NUMBER_OF_AXES (16) // Maximum number of axes 365 | #define VJOY_NUMBER_OF_HAT (4) // Maximum number of hats 366 | #define VJOY_NUMBER_OF_BUTTONS (128) // Maximum number of hat 367 | #define VJOY_AXIS_MAX_VALUE (0x7FFF) // Maximum value for an axis 368 | 369 | #endif 370 | 371 | 372 | //---------------------------------------------------------- 373 | // FFB Features to be placed in vJoy's driver memory context 374 | 375 | // Uncomment this to have a wrong HID descriptor for the FFB 376 | // This allows to make the device to not being reported as a 377 | // FFB device! 378 | //#define VJOY_FORCE_WRONG_FFB_HID 379 | 380 | // Max 1..128 effect block index, 10 simultaneously played 381 | #define VJOY_FFB_FIRST_EFFECT_ID (1) 382 | #define VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX (100) 383 | #define VJOY_FFB_MAX_SIMULTANEOUS_EFFECTS (10) 384 | 385 | #define VJOY_FFB_EFFECT_FREE (0x00) 386 | #define VJOY_FFB_EFFECT_ALLOCATED (0x01) 387 | 388 | 389 | // For HID descriptor : use only X, or X/Y axes for FFB 390 | // Can be 1 (X) or 2 (X and Y) - see ESP Wheel source code 391 | #define FFB_USE_XY_AXES (2) 392 | 393 | // Turn on 1 byte packing of struct, dummy fields will be added when needed 394 | #include 395 | 396 | // FFB: PID Block Load Feature Report=2 397 | // Only one per device 398 | typedef struct _FFB_PID_BLOCK_LOAD_REPORT 399 | { 400 | // 1..100. If 0, then error (LoadStatus below is 2 or 3) 401 | BYTE EffectBlockIndex; 402 | // 0 ongoing, 1=Success,2=Full,3=Error 403 | BYTE LoadStatus; 404 | // equals =0 if full, or sizeof(FFB_PID_EFFECT_STATE_REPORT) * (100 - created) max is VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX 405 | USHORT RAMPoolAvailable; 406 | } FFB_PID_BLOCK_LOAD_REPORT, * PFFB_PID_BLOCK_LOAD_REPORT; 407 | 408 | // FFB: PID Pool Feature Report=3 409 | typedef struct _FFB_PID_POOL_REPORT 410 | { 411 | // equals sizeof(FFB_PID_EFFECT_STATE_REPORT) * 100, max is 0xFFFF 412 | USHORT RAMPoolSize; 413 | // 0..10 414 | BYTE MaxSimultaneousEffects; 415 | // Bit 0=DeviceManagedPool, bit 1=SharedParameterBlocks 416 | BYTE MemoryManagement; 417 | } FFB_PID_POOL_REPORT, * PFFB_PID_POOL_REPORT; 418 | 419 | // FFB: PID Effect State Report 420 | // Up to MAX_FFB_EFFECTS_BLOCK_INDEX per device 421 | typedef struct _FFB_PID_EFFECT_STATE_REPORT 422 | { 423 | // Bitfield for PID Effect State Report, bit: 424 | // 0: Effect Playing 425 | // 1: Device Paused 426 | // 2: Actuators Enabled 427 | // 3: Safety Switch 428 | // 4: Actuator Override Switch 429 | // 5: Actuator Power 430 | BYTE PIDEffectStateReport; 431 | // For CreateEffect/GetNextFree(), internal to driver side 432 | // 0: free, 1 allocated (in use) 433 | BYTE InUse; 434 | } FFB_PID_EFFECT_STATE_REPORT, * PFFB_PID_EFFECT_STATE_REPORT; 435 | 436 | // All FFB PID data, one per device 437 | // This struct will be transfered between vJoy and client application 438 | typedef struct _FFB_DEVICE_PID 439 | { 440 | FFB_PID_BLOCK_LOAD_REPORT PIDBlockLoad; 441 | FFB_PID_POOL_REPORT PIDPool; 442 | // All Effect States, as 2-bytes 443 | FFB_PID_EFFECT_STATE_REPORT EffectStates[VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX]; 444 | // Index to next free slot, between 1 and VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX+1 445 | // If >VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX => full 446 | BYTE NextFreeEID; 447 | // Index to last valid slot that has been used, between 1 to VJOY_FFB_MAX_EFFECTS_BLOCK_INDEX 448 | // 0 if no effect yet created/used 449 | BYTE LastEID; 450 | } FFB_DEVICE_PID, * PFFB_DEVICE_PID; 451 | 452 | // Turn off 1-byte packing of struct 453 | #include 454 | 455 | // HID Descriptor definitions - Axes 456 | #define HID_USAGE_X 0x30 457 | #define HID_USAGE_Y 0x31 458 | #define HID_USAGE_Z 0x32 459 | #define HID_USAGE_RX 0x33 460 | #define HID_USAGE_RY 0x34 461 | #define HID_USAGE_RZ 0x35 462 | #define HID_USAGE_SL0 0x36 463 | #define HID_USAGE_SL1 0x37 464 | #define HID_USAGE_WHL 0x38 465 | #define HID_USAGE_POV 0x39 466 | #define HID_USAGE_ACCELERATOR 0xC4 467 | #define HID_USAGE_BRAKE 0xC5 468 | #define HID_USAGE_CLUTCH 0xC6 469 | #define HID_USAGE_STEERING 0xC8 470 | #define HID_USAGE_AILERON 0xB0 471 | #define HID_USAGE_RUDDER 0xBA 472 | #define HID_USAGE_THROTTLE 0xBB 473 | 474 | 475 | 476 | // HID Descriptor definitions - FFB Effects 477 | #define HID_USAGE_CONST 0x26 // Usage ET Constant Force 478 | #define HID_USAGE_RAMP 0x27 // Usage ET Ramp 479 | #define HID_USAGE_SQUR 0x30 // Usage ET Square 480 | #define HID_USAGE_SINE 0x31 // Usage ET Sine 481 | #define HID_USAGE_TRNG 0x32 // Usage ET Triangle 482 | #define HID_USAGE_STUP 0x33 // Usage ET Sawtooth Up 483 | #define HID_USAGE_STDN 0x34 // Usage ET Sawtooth Down 484 | #define HID_USAGE_SPRNG 0x40 // Usage ET Spring 485 | #define HID_USAGE_DMPR 0x41 // Usage ET Damper 486 | #define HID_USAGE_INRT 0x42 // Usage ET Inertia 487 | #define HID_USAGE_FRIC 0x43 // Usage ET Friction 488 | #define HID_USAGE_CUSTM 0x28 // Usage ET Custom Force Data 489 | #define HID_USAGE_RESERVD 0x29 // Usage ET Reserved (unused) 490 | 491 | #define HID_USAGE_BLKLD_SUCCESS 0x8C // Usage Block Load Success 492 | #define HID_USAGE_BLKLD_FULL 0x8D // Usage Block Load Full 493 | #define HID_USAGE_BLKLD_ERROR 0x8E // Usage Block Load Error 494 | 495 | // HID Descriptor definitions - FFB Report IDs 496 | #define HID_ID_EFFREP 0x01 // Usage Set Effect Report 497 | #define HID_ID_ENVREP 0x02 // Usage Set Envelope Report 498 | #define HID_ID_CONDREP 0x03 // Usage Set Condition Report 499 | #define HID_ID_PRIDREP 0x04 // Usage Set Periodic Report 500 | #define HID_ID_CONSTREP 0x05 // Usage Set Constant Force Report 501 | #define HID_ID_RAMPREP 0x06 // Usage Set Ramp Force Report 502 | #define HID_ID_CSTMREP 0x07 // Usage Custom Force Data Report 503 | #define HID_ID_SMPLREP 0x08 // Usage Download Force Sample 504 | #define HID_ID_EFOPREP 0x0A // Usage Effect Operation Report 505 | #define HID_ID_BLKFRREP 0x0B // Usage PID Block Free Report 506 | #define HID_ID_CTRLREP 0x0C // Usage PID Device Control 507 | #define HID_ID_GAINREP 0x0D // Usage Device Gain Report 508 | #define HID_ID_SETCREP 0x0E // Usage Set Custom Force Report 509 | #define HID_ID_NEWEFREP 0x01 // Usage Create New Effect Report 510 | #define HID_ID_BLKLDREP 0x02 // Usage Block Load Report 511 | #define HID_ID_POOLREP 0x03 // Usage PID Pool Report 512 | #define HID_ID_STATEREP 0x04 // Usage PID State Report 513 | 514 | #endif 515 | --------------------------------------------------------------------------------