diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56eedcf..95b0de8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,41 @@ name: Build and upload -on: workflow_dispatch +on: + workflow_dispatch: + push: + tags: + - "v*" + +permissions: + contents: write jobs: - empty: - runs-on: ubuntu-latest + build: + name: ${{ matrix.platform.name }} + strategy: + fail-fast: false + matrix: + platform: + - { name: 'Linux (x64)', os: ubuntu-latest } + - { name: 'Linux (arm64)', os: ubuntu-24.04-arm } + - { name: 'Windows (x64)', os: windows-latest } + - { name: 'Mac (x64)', os: macos-15-intel } + - { name: 'Mac (arm64)', os: macos-15 } + runs-on: ${{ matrix.platform.os }} steps: - - run: 'echo "empty"' + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - if: ${{ startsWith(matrix.platform.os, 'macos-') }} + run: brew update && ./scripts/install-deps-mac.sh + + - if: ${{ startsWith(matrix.platform.os, 'ubuntu-') }} + run: sudo apt-get update && sudo ./scripts/install-deps-ubuntu.sh -# This file is empty on the master branch. Releases are made from the 'workflow' branch. + - env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CROSS_COMPILE_ARCH: ${{ matrix.platform.arch }} + run: npm run release diff --git a/package.json b/package.json index b909a06..f5ac2d0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.11.13", + "version": "0.11.13-rawaxis.1", "name": "@kmamal/sdl", "description": "SDL bindings for Node.js", "keywords": [ @@ -38,7 +38,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+ssh://git@github.com/kmamal/node-sdl.git" + "url": "git+ssh://git@github.com/rafaellehmkuhl/node-sdl.git" }, "main": "./src/javascript/index.js", "types": "./src/types/index.d.ts", diff --git a/src/javascript/controller/controller-instance.js b/src/javascript/controller/controller-instance.js index 5ddb5a1..0046b3c 100644 --- a/src/javascript/controller/controller-instance.js +++ b/src/javascript/controller/controller-instance.js @@ -13,12 +13,13 @@ const validEvents = [ ] class ControllerInstance extends EventsViaPoll { - constructor (device) { + constructor (device, options = {}) { super(validEvents) if (!Globals.controllerDevices.includes(device)) { throw Object.assign(new Error("invalid device"), { device }) } - const result = Bindings.controller_open(device._index) + const { rawAxisMode = false } = options + const result = Bindings.controller_open(device._index, rawAxisMode) this._firmwareVersion = result.firmwareVersion this._serialNumber = result.serialNumber diff --git a/src/javascript/controller/index.js b/src/javascript/controller/index.js index c84cf76..00b6176 100644 --- a/src/javascript/controller/index.js +++ b/src/javascript/controller/index.js @@ -13,7 +13,7 @@ const controller = new class extends EventsViaPoll { return Globals.controllerDevices } - openDevice (device) { return new ControllerInstance(device) } + openDevice (device, options = {}) { return new ControllerInstance(device, options) } addMappings (mappings) { if (!Array.isArray(mappings)) { throw Object.assign(new Error("mappings must be an array"), { mappings }) } diff --git a/src/javascript/joystick/index.js b/src/javascript/joystick/index.js index 84ab7bf..25cc548 100644 --- a/src/javascript/joystick/index.js +++ b/src/javascript/joystick/index.js @@ -28,7 +28,7 @@ const joystick = new class extends EventsViaPoll { return Globals.joystickDevices } - openDevice (device) { return new JoystickInstance(device) } + openDevice (device, options = {}) { return new JoystickInstance(device, options) } }() module.exports = { joystick } diff --git a/src/javascript/joystick/joystick-instance.js b/src/javascript/joystick/joystick-instance.js index 7b633f6..20fa548 100644 --- a/src/javascript/joystick/joystick-instance.js +++ b/src/javascript/joystick/joystick-instance.js @@ -13,12 +13,13 @@ const validEvents = [ ] class JoystickInstance extends EventsViaPoll { - constructor (device) { + constructor (device, options = {}) { super(validEvents) if (!Globals.joystickDevices.includes(device)) { throw Object.assign(new Error("invalid device"), { device }) } - const result = Bindings.joystick_open(device._index) + const { rawAxisMode = false } = options + const result = Bindings.joystick_open(device._index, rawAxisMode) this._firmwareVersion = result.firmwareVersion this._serialNumber = result.serialNumber diff --git a/src/native/controller.cpp b/src/native/controller.cpp index a00a22b..c6a64ea 100644 --- a/src/native/controller.cpp +++ b/src/native/controller.cpp @@ -124,6 +124,7 @@ controller::open (const Napi::CallbackInfo &info) Napi::Env env = info.Env(); int index = info[0].As().Int32Value(); + bool rawAxisMode = info.Length() > 1 && info[1].As().Value(); SDL_GameController *controller = SDL_GameControllerOpen(index); if (controller == nullptr) { @@ -133,6 +134,12 @@ controller::open (const Napi::CallbackInfo &info) throw Napi::Error::New(env, message.str()); } + SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller); + int joystick_id = SDL_JoystickInstanceID(joystick); + if (rawAxisMode) { + joystick::rawAxisModeDevices.insert(joystick_id); + } + // SDL_GameControllerOpen produces errors even though it succeeds const char *error = SDL_GetError(); if (error != global::no_error) { fprintf(stderr, "SDL silent error: %s\n", error); } @@ -154,7 +161,6 @@ controller::open (const Napi::CallbackInfo &info) Napi::Value steam_handle = getSteamHandle(env, controller); - SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller); Napi::Value power = joystick::getPowerLevel(env, joystick); Napi::Object result = Napi::Object::New(env); @@ -186,6 +192,10 @@ controller::close (const Napi::CallbackInfo &info) throw Napi::Error::New(env, message.str()); } + SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller); + int joystick_id = SDL_JoystickInstanceID(joystick); + joystick::rawAxisModeDevices.erase(joystick_id); + SDL_GameControllerClose(controller); return env.Undefined(); diff --git a/src/native/joystick.cpp b/src/native/joystick.cpp index 3b52cfc..e47c8cd 100644 --- a/src/native/joystick.cpp +++ b/src/native/joystick.cpp @@ -5,12 +5,14 @@ #include #include #include +#include std::map joystick::hat_positions; std::map joystick::types; std::map joystick::power_levels; SDL_JoystickGUID joystick::zero_guid; +std::set joystick::rawAxisModeDevices; double joystick::mapAxis (SDL_Joystick *joystick, int axis) { @@ -19,12 +21,20 @@ joystick::mapAxis (SDL_Joystick *joystick, int axis) { double joystick::mapAxisValue (SDL_Joystick *joystick, int axis, int value) { - Sint16 initial; - if (!SDL_JoystickGetAxisInitialState(joystick, axis, &initial)) { initial = 0; } - double range = value < initial - ? initial - SDL_JOYSTICK_AXIS_MIN - : SDL_JOYSTICK_AXIS_MAX - initial; - return (value - initial) / range; + int joystick_id = SDL_JoystickInstanceID(joystick); + bool rawMode = rawAxisModeDevices.count(joystick_id) > 0; + + if (!rawMode) { + Sint16 initial; + if (!SDL_JoystickGetAxisInitialState(joystick, axis, &initial)) { initial = 0; } + double range = value < initial + ? initial - SDL_JOYSTICK_AXIS_MIN + : SDL_JOYSTICK_AXIS_MAX - initial; + return (value - initial) / range; + } + + double range = value < 0 ? -SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX; + return value / range; } Napi::Array @@ -176,6 +186,7 @@ joystick::open (const Napi::CallbackInfo &info) Napi::Env env = info.Env(); int index = info[0].As().Int32Value(); + bool rawAxisMode = info.Length() > 1 && info[1].As().Value(); SDL_Joystick *joystick = SDL_JoystickOpen(index); if (joystick == nullptr) { @@ -185,6 +196,11 @@ joystick::open (const Napi::CallbackInfo &info) throw Napi::Error::New(env, message.str()); } + int joystick_id = SDL_JoystickInstanceID(joystick); + if (rawAxisMode) { + rawAxisModeDevices.insert(joystick_id); + } + // SDL_JoystickOpen produces errors even though it succeeds const char *error = SDL_GetError(); if (error != global::no_error) { fprintf(stderr, "SDL silent error: %s\n", error); } @@ -419,6 +435,7 @@ joystick::close (const Napi::CallbackInfo &info) throw Napi::Error::New(env, message.str()); } + rawAxisModeDevices.erase(joystick_id); SDL_JoystickClose(joystick); return env.Undefined(); diff --git a/src/native/joystick.h b/src/native/joystick.h index 89504c4..b89afc0 100644 --- a/src/native/joystick.h +++ b/src/native/joystick.h @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace joystick { @@ -12,6 +13,7 @@ namespace joystick { extern std::map hat_positions; extern std::map power_levels; extern SDL_JoystickGUID zero_guid; + extern std::set rawAxisModeDevices; double mapAxis (SDL_Joystick *joystick, int axis); double mapAxisValue (SDL_Joystick *joystick, int axis, int value); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 08c5889..e632e97 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -956,6 +956,15 @@ export namespace Sdl { export namespace Joystick { + export interface OpenOptions { + /** + * When true, axis values are normalized without SDL's initial state calibration. + * This avoids incorrect zero-point calibration when joysticks aren't centered at startup. + * @default false + */ + rawAxisMode?: boolean + } + export interface BallPosition { readonly x: number readonly y: number @@ -1050,13 +1059,22 @@ export namespace Sdl { readonly devices: Device[] - openDevice (device: Device): JoystickInstance + openDevice (device: Device, options?: OpenOptions): JoystickInstance } } export namespace Controller { + export interface OpenOptions { + /** + * When true, axis values are normalized without SDL's initial state calibration. + * This avoids incorrect zero-point calibration when controllers aren't centered at startup. + * @default false + */ + rawAxisMode?: boolean + } + export type ControllerType = null | 'xbox360' @@ -1190,7 +1208,7 @@ export namespace Sdl { readonly devices: Device[] - openDevice (device: Device): ControllerInstance + openDevice (device: Device, options?: OpenOptions): ControllerInstance } }