diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index c09908c..9ef9278 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -14,11 +14,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Android diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index c73cbbc..8c9ce9d 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -11,11 +11,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Android diff --git a/.github/workflows/publish_snapshot.yml b/.github/workflows/publish_snapshot.yml index c0dabee..b52979d 100644 --- a/.github/workflows/publish_snapshot.yml +++ b/.github/workflows/publish_snapshot.yml @@ -12,11 +12,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Android diff --git a/LICENSE_hartmann b/LICENSE_hartmann new file mode 100644 index 0000000..dc54a65 --- /dev/null +++ b/LICENSE_hartmann @@ -0,0 +1,18 @@ +Jamepad +Copyright (C) 2016 William Hartman + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md index e07b3f0..3da94ae 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,4 @@ You can get help on the [libgdx discord](https://discord.gg/6pgDK9F). ## License The project is licensed under the Apache 2 License, meaning you can use it free of charge, without strings attached in commercial and non-commercial projects. We love to get (non-mandatory) credit in case you release a game or app using this project! +Some code in the `desktop` project is forked and modified from the original work by [William Hartman](https://github.com/williamahartman/Jamepad). These classes retained author notice. diff --git a/build.gradle b/build.gradle index c13359f..d99d278 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath "org.docstr:gwt-gradle-plugin:1.1.29" - classpath "com.android.tools.build:gradle:7.2.2" + classpath "com.android.tools.build:gradle:8.7.3" } } @@ -18,9 +18,9 @@ allprojects { ext { appName = 'gdx-controllers' - gdxVersion = '1.9.11' - roboVMVersion = '2.3.19' - jamepadVersion = '2.26.5.0' + gdxVersion = '1.13.1' + roboVMVersion = '2.3.23' + lwjgl3Version = '3.4.0-SNAPSHOT' isReleaseBuild = { return project.hasProperty("RELEASE") diff --git a/gdx-controllers-android/AndroidManifest.xml b/gdx-controllers-android/AndroidManifest.xml index 8183a78..1bc5159 100644 --- a/gdx-controllers-android/AndroidManifest.xml +++ b/gdx-controllers-android/AndroidManifest.xml @@ -1,6 +1,5 @@ - + \ No newline at end of file diff --git a/gdx-controllers-android/build.gradle b/gdx-controllers-android/build.gradle index 79fedae..071bfc0 100644 --- a/gdx-controllers-android/build.gradle +++ b/gdx-controllers-android/build.gradle @@ -3,12 +3,12 @@ apply plugin: 'com.android.library' project.group = 'com.badlogicgames.gdx-controllers' android { - - compileSdkVersion 29 + namespace "com.badlogic.gdx.controllers" + compileSdk 35 defaultConfig { - minSdkVersion 16 - targetSdkVersion 29 + minSdkVersion 21 + targetSdkVersion 35 } sourceSets { diff --git a/gdx-controllers-android/src/com/badlogic/gdx/controllers/android/AndroidControllers.java b/gdx-controllers-android/src/com/badlogic/gdx/controllers/android/AndroidControllers.java index dfaa310..a8f0bca 100644 --- a/gdx-controllers-android/src/com/badlogic/gdx/controllers/android/AndroidControllers.java +++ b/gdx-controllers-android/src/com/badlogic/gdx/controllers/android/AndroidControllers.java @@ -288,7 +288,7 @@ public boolean onKey (View view, int keyCode, KeyEvent keyEvent) { event.code = keyCode; eventQueue.add(event); } - return keyCode != KeyEvent.KEYCODE_BACK || Gdx.input.isCatchBackKey(); + return keyCode != KeyEvent.KEYCODE_BACK || Gdx.input.isCatchKey(keyCode); } else { return false; } diff --git a/gdx-controllers-core/build.gradle b/gdx-controllers-core/build.gradle index c439403..b1b19b6 100644 --- a/gdx-controllers-core/build.gradle +++ b/gdx-controllers-core/build.gradle @@ -5,7 +5,7 @@ eclipse { } dependencies { - implementation "com.badlogicgames.gdx:gdx:$gdxVersion" + api "com.badlogicgames.gdx:gdx:$gdxVersion" } targetCompatibility = 1.7 diff --git a/gdx-controllers-desktop/build.gradle b/gdx-controllers-desktop/build.gradle index 2665e1f..4b73cd1 100644 --- a/gdx-controllers-desktop/build.gradle +++ b/gdx-controllers-desktop/build.gradle @@ -49,13 +49,19 @@ tasks.getByName('processResources') { dependencies { api project(":gdx-controllers-core") implementation "com.badlogicgames.gdx:gdx:$gdxVersion" - api("com.badlogicgames.jamepad:jamepad:$jamepadVersion") { - exclude group: 'com.badlogicgames.gdx', module: 'gdx-jnigen-loader' - } + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-linux" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-linux-arm32" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-linux-arm64" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-linux-riscv64" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-macos" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-macos-arm64" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-windows" + implementation "org.lwjgl:lwjgl-sdl:$lwjgl3Version:natives-windows-x86" } -targetCompatibility = 1.7 -sourceCompatibility = 1.7 +targetCompatibility = 1.8 +sourceCompatibility = 1.8 ext { diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/Configuration.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/Configuration.java new file mode 100644 index 0000000..a08ee49 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/Configuration.java @@ -0,0 +1,27 @@ +package com.badlogic.gdx.controllers.desktop; + +import com.badlogic.gdx.controllers.ControllerManager; + +/** + * Class defining the configuration of a {@link ControllerManager}. + * + * @author Benjamin Schulte + */ +public class Configuration { + /** + * The max number of controllers the ControllerManager should deal with + */ + public int maxNumControllers = 4; + + /** + * Use RawInput implementation instead of XInput on Windows, if applicable. Enable this if you + * need to use more than four XInput controllers at once. Comes with drawbacks. + */ + public boolean useRawInput = false; + + /** + * Disable this to return to legacy temporary file loading of database file. + */ + public boolean loadDatabaseInMemory = true; +} + diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/JamepadControllerManager.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/JamepadControllerManager.java index d7706b2..103904f 100644 --- a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/JamepadControllerManager.java +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/JamepadControllerManager.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.controllers.desktop.support.CompositeControllerListener; import com.badlogic.gdx.controllers.desktop.support.JamepadControllerMonitor; import com.badlogic.gdx.controllers.desktop.support.JamepadShutdownHook; +import com.badlogic.gdx.controllers.desktop.support.SDLControllerManager; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; @@ -14,10 +15,10 @@ public class JamepadControllerManager extends AbstractControllerManager implements Disposable { // assign a Jamepad configuration to this field at game startup to override defaults - public static com.studiohartman.jamepad.Configuration jamepadConfiguration; + public static Configuration jamepadConfiguration; private static boolean nativeLibInitialized = false; - private static com.studiohartman.jamepad.ControllerManager controllerManager; + private static SDLControllerManager controllerManager; private final CompositeControllerListener compositeListener = new CompositeControllerListener(); @@ -26,10 +27,10 @@ public JamepadControllerManager() { if (!nativeLibInitialized) { if (jamepadConfiguration == null) { - jamepadConfiguration = new com.studiohartman.jamepad.Configuration(); + jamepadConfiguration = new Configuration(); } - controllerManager = new com.studiohartman.jamepad.ControllerManager(jamepadConfiguration); + controllerManager = new SDLControllerManager(jamepadConfiguration); controllerManager.initSDLGamepad(); JamepadControllerMonitor monitor = new JamepadControllerMonitor(controllerManager, compositeListener); @@ -70,7 +71,7 @@ public void dispose() { } /** - * @see com.studiohartman.jamepad.ControllerManager#addMappingsFromFile(String) + * @see SDLControllerManager#addMappingsFromFile(String) */ public static void addMappingsFromFile(String path) throws IOException, IllegalStateException { controllerManager.addMappingsFromFile(path); diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerAxis.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerAxis.java new file mode 100644 index 0000000..964a37b --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerAxis.java @@ -0,0 +1,46 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import org.lwjgl.sdl.SDLGamepad; + +/** + * The list of axes available on a gamepad + * + * Thumbstick axis values range from SDL_JOYSTICK_AXIS_MIN to + * SDL_JOYSTICK_AXIS_MAX, and are centered within ~8000 of zero, though + * advanced UI will allow users to set or autodetect the dead zone, which + * varies between gamepads. + * + * Trigger axis values range from 0 (released) to SDL_JOYSTICK_AXIS_MAX (fully + * pressed) when reported by SDL_GetGamepadAxis(). Note that this is not the + * same range that will be reported by the lower-level SDL_GetJoystickAxis(). + */ +public enum ControllerAxis { + INVALID(SDLGamepad.SDL_GAMEPAD_AXIS_INVALID), + LEFTX(SDLGamepad.SDL_GAMEPAD_AXIS_LEFTX), + LEFTY(SDLGamepad.SDL_GAMEPAD_AXIS_LEFTY), + RIGHTX(SDLGamepad.SDL_GAMEPAD_AXIS_RIGHTX), + RIGHTY(SDLGamepad.SDL_GAMEPAD_AXIS_RIGHTY), + LEFT_TRIGGER(SDLGamepad.SDL_GAMEPAD_AXIS_LEFT_TRIGGER), + RIGHT_TRIGGER(SDLGamepad.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),; + + public static final ControllerAxis[] VALUES = values(); + + private final int id; + + ControllerAxis(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static ControllerAxis getById(int id) { + for (ControllerAxis axis : VALUES) { + if (axis.id == id) + return axis; + } + + return null; + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButton.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButton.java new file mode 100644 index 0000000..bcfaf20 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButton.java @@ -0,0 +1,77 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import org.lwjgl.sdl.SDLGamepad; + +/** + * The list of buttons available on a gamepad + * + * For controllers that use a diamond pattern for the face buttons, the + * south/east/west/north buttons below correspond to the locations in the + * diamond pattern. For Xbox controllers, this would be A/B/X/Y, for Nintendo + * Switch controllers, this would be B/A/Y/X, for GameCube controllers this + * would be A/X/B/Y, for PlayStation controllers this would be + * Cross/Circle/Square/Triangle. + * + * For controllers that don't use a diamond pattern for the face buttons, the + * south/east/west/north buttons indicate the buttons labeled A, B, C, D, or + * 1, 2, 3, 4, or for controllers that aren't labeled, they are the primary, + * secondary, etc. buttons. + * + * The activate action is often the south button and the cancel action is + * often the east button, but in some regions this is reversed, so your game + * should allow remapping actions based on user preferences. + * + * You can query the labels for the face buttons using + * SDL_GetGamepadButtonLabel() + * */ +public enum ControllerButton { + + INVALID(SDLGamepad.SDL_GAMEPAD_BUTTON_INVALID), + SOUTH(SDLGamepad.SDL_GAMEPAD_BUTTON_SOUTH), /** Bottom face button (e.g. Xbox A button) */ + EAST(SDLGamepad.SDL_GAMEPAD_BUTTON_EAST), /** Right face button (e.g. Xbox B button) */ + WEST(SDLGamepad.SDL_GAMEPAD_BUTTON_WEST), /** Left face button (e.g. Xbox X button) */ + NORTH(SDLGamepad.SDL_GAMEPAD_BUTTON_NORTH), /** Top face button (e.g. Xbox Y button) */ + BACK(SDLGamepad.SDL_GAMEPAD_BUTTON_BACK), + GUIDE(SDLGamepad.SDL_GAMEPAD_BUTTON_GUIDE), + START(SDLGamepad.SDL_GAMEPAD_BUTTON_START), + LEFT_STICK(SDLGamepad.SDL_GAMEPAD_BUTTON_LEFT_STICK), + RIGHT_STICK(SDLGamepad.SDL_GAMEPAD_BUTTON_RIGHT_STICK), + LEFT_SHOULDER(SDLGamepad.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), + RIGHT_SHOULDER(SDLGamepad.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), + DPAD_UP(SDLGamepad.SDL_GAMEPAD_BUTTON_DPAD_UP), + DPAD_DOWN(SDLGamepad.SDL_GAMEPAD_BUTTON_DPAD_DOWN), + DPAD_LEFT(SDLGamepad.SDL_GAMEPAD_BUTTON_DPAD_LEFT), + DPAD_RIGHT(SDLGamepad.SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + MISC1(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC1), /** Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button, Google Stadia capture button) */ + RIGHT_PADDLE1(SDLGamepad.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1), /** Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */ + LEFT_PADDLE1(SDLGamepad.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1), /** Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */ + RIGHT_PADDLE2(SDLGamepad.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2), /** Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */ + LEFT_PADDLE2(SDLGamepad.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2), /** Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */ + TOUCHPAD(SDLGamepad.SDL_GAMEPAD_BUTTON_TOUCHPAD), /** PS4/PS5 touchpad button */ + MISC2(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC2), /** Additional button */ + MISC3(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC3), /** Additional button */ + MISC4(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC4), /** Additional button */ + MISC5(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC5), /** Additional button */ + MISC6(SDLGamepad.SDL_GAMEPAD_BUTTON_MISC6),; /** Additional button */ + + public static final ControllerButton[] VALUES = values(); + + private final int id; + + ControllerButton(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static ControllerButton getById(int id) { + for (ControllerButton button : VALUES) { + if (button.id == id) + return button; + } + + return null; + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButtonLabel.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButtonLabel.java new file mode 100644 index 0000000..5dbae04 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerButtonLabel.java @@ -0,0 +1,45 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import org.lwjgl.sdl.SDLGamepad; + +/** + * The set of gamepad button labels + * + * This isn't a complete set, just the face buttons to make it easy to show + * button prompts. + * + * For a complete set, you should look at the button and gamepad type and have + * a set of symbols that work well with your art style. + */ +public enum ControllerButtonLabel { + UNKNOWN(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN), + A(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_A), + B(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_B), + X(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_X), + Y(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_Y), + CROSS(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_CROSS), + CIRCLE(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_CIRCLE), + SQUARE(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_SQUARE), + TRIANGLE(SDLGamepad.SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE),; + + public static final ControllerButtonLabel[] VALUES = values(); + + private final int id; + + ControllerButtonLabel(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static ControllerButtonLabel getById(int id) { + for (ControllerButtonLabel label : VALUES) { + if (label.id == id) + return label; + } + + return null; + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerIndex.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerIndex.java new file mode 100644 index 0000000..92b4c51 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerIndex.java @@ -0,0 +1,311 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import com.badlogic.gdx.controllers.ControllerPowerLevel; +import static org.lwjgl.sdl.SDLGamepad.*; +import static org.lwjgl.sdl.SDLJoystick.*; +import static org.lwjgl.sdl.SDLProperties.*; + +import org.lwjgl.system.MemoryStack; + +import java.nio.IntBuffer; + +/** + * This class is the main thing you're gonna need to deal with if you want lots of + * control over your gamepads or want to avoid lots of ControllerState allocations. + * + * A Controller index cannot be made from outside the Jamepad package. You're gonna need to go + * through a ControllerManager to get your controllers. + * + * A ControllerIndex represents the controller at a given index. There may or may not actually + * be a controller at that index. Exceptions are thrown if the controller is not connected. + * + * @author William Hartman + */ +public final class ControllerIndex { + + private static final float AXIS_MAX_VAL = 32767; + private int index; + private long controllerPtr; + + private boolean[] heldDownButtons; + private boolean[] justPressedButtons; + + /** + * Constructor. Builds a controller at the given index and attempts to connect to it. + * This is only accessible in the Jamepad package, so people can't go trying to make controllers + * before the native library is loaded or initialized. + * + * @param index The index of the controller + */ + ControllerIndex(int index) { + this.index = index; + + heldDownButtons = new boolean[ControllerButton.VALUES.length]; + justPressedButtons = new boolean[ControllerButton.VALUES.length]; + for(int i = 0; i < heldDownButtons.length; i++) { + heldDownButtons[i] = false; + justPressedButtons[i] = false; + } + + connectController(); + } + private void connectController() { + controllerPtr = SDL_OpenGamepad(index); + } + + /** + * Close the connection to this controller. + */ + public void close() { + if(controllerPtr != 0) { + if (SDL_GamepadConnected(controllerPtr)) + SDL_CloseGamepad(controllerPtr); + controllerPtr = 0; + } + } + + /** + * Close and reconnect to the native gamepad at the index associated with this ControllerIndex object. + * This is will refresh the gamepad represented here. This should be called if something is plugged + * in or unplugged. + * + * @return whether or not the controller could successfully reconnect. + */ + public boolean reconnectController() { + close(); + connectController(); + + return isConnected(); + } + + /** + * Return whether or not the controller is currently connected. This first checks that the controller + * was successfully connected to our SDL backend. Then we check if the controller is currently plugged + * in. + * + * @return Whether or not the controller is plugged in. + */ + public boolean isConnected() { + if (controllerPtr == 0) + return false; + return SDL_GamepadConnected(controllerPtr); + } + + /** + * Returns the index of the current controller. + * @return The index of the current controller. + */ + public int getIndex() { + return index; + } + + /** + * @return true of controller can vibrate + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean canVibrate() throws ControllerUnpluggedException { + ensureConnected(); + int propertyId = SDL_GetGamepadProperties(controllerPtr); + return SDL_GetBooleanProperty(propertyId, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false); + } + + /** + * Vibrate the controller using the new rumble API + * Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling. + * + * This will return false if the controller doesn't support vibration or if SDL was unable to start + * vibration (maybe the controller doesn't support left/right vibration, maybe it was unplugged in the + * middle of trying, etc...) + * + * @param leftMagnitude The intensity of the left rumble motor (this should be between 0 and 1) + * @param rightMagnitude The intensity of the right rumble motor (this should be between 0 and 1) + * @return Whether or not the controller was able to be vibrated (i.e. if haptics are supported) + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean doVibration(float leftMagnitude, float rightMagnitude, int duration_ms) throws ControllerUnpluggedException { + ensureConnected(); + + //Check the values are appropriate + boolean leftInRange = leftMagnitude >= 0 && leftMagnitude <= 1; + boolean rightInRange = rightMagnitude >= 0 && rightMagnitude <= 1; + if(!(leftInRange && rightInRange)) { + throw new IllegalArgumentException("The passed values are not in the range 0 to 1!"); + } + + return SDL_RumbleGamepad(controllerPtr, (short) (65535 * leftMagnitude), (short) (65535 * rightMagnitude), duration_ms); + } + + /** + * Returns whether or not a given button has been pressed. + * + * @param toCheck The ControllerButton to check the state of + * @return Whether or not the button is pressed. + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean isButtonPressed(ControllerButton toCheck) throws ControllerUnpluggedException { + if (toCheck == ControllerButton.INVALID) + return false; + updateButton(toCheck.getId()); + return heldDownButtons[toCheck.getId()]; + } + + /** + * Returns whether or not a given button has just been pressed since you last made a query + * about that button (either through this method, isButtonPressed(), or through the ControllerState + * side of things). If the button was not pressed the last time you checked but is now, this method + * will return true. + * + * @param toCheck The ControllerButton to check the state of + * @return Whether or not the button has just been pressed. + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean isButtonJustPressed(ControllerButton toCheck) throws ControllerUnpluggedException { + if (toCheck == ControllerButton.INVALID) + return false; + + updateButton(toCheck.getId()); + return justPressedButtons[toCheck.getId()]; + } + + private void updateButton(int buttonIndex) throws ControllerUnpluggedException { + if (buttonIndex < 0) + return; + ensureConnected(); + + SDL_UpdateGamepads(); + + boolean currButtonIsPressed = SDL_GetGamepadButton(controllerPtr, buttonIndex); + justPressedButtons[buttonIndex] = (currButtonIsPressed && !heldDownButtons[buttonIndex]); + heldDownButtons[buttonIndex] = currButtonIsPressed; + } + + /** + * Returns if a given button is available on controller. + * + * @param toCheck The ControllerButton to check + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean isButtonAvailable(ControllerButton toCheck) throws ControllerUnpluggedException { + ensureConnected(); + return SDL_GamepadHasButton(controllerPtr, toCheck.getId()); + } + + /** + * Returns the current state of a passed axis. + * + * @param toCheck The ControllerAxis to check the state of + * @return The current state of the requested axis. + * @throws ControllerUnpluggedException If the controller is not connected + */ + public float getAxisState(ControllerAxis toCheck) throws ControllerUnpluggedException { + ensureConnected(); + + SDL_UpdateGamepads(); + return SDL_GetGamepadAxis(controllerPtr, toCheck.getId()) / AXIS_MAX_VAL; + } + + /** + * Returns if passed axis is available on controller. + * + * @param toCheck The ControllerAxis to check + * @throws ControllerUnpluggedException If the controller is not connected + */ + public boolean isAxisAvailable(ControllerAxis toCheck) throws ControllerUnpluggedException { + ensureConnected(); + return SDL_GamepadHasAxis(controllerPtr, toCheck.getId()); + } + + /** + * Returns the implementation dependent name of this controller. + * + * @return The the name of this controller + * @throws ControllerUnpluggedException If the controller is not connected + */ + public String getName() throws ControllerUnpluggedException { + ensureConnected(); + + String controllerName = SDL_GetGamepadName(controllerPtr); + + //Return a descriptive string instead of null if the attached controller does not have a name + if(controllerName == null) { + return "Unnamed Controller"; + } + return controllerName; + } + + /** + * Returns the instance ID of the current controller, which uniquely identifies + * the device from the time it is connected until it is disconnected. + * + * @return The instance ID of the current controller + * @throws ControllerUnpluggedException If the controller is not connected + */ + public int getDeviceInstanceID() throws ControllerUnpluggedException { + ensureConnected(); + long joystick = SDL_GetGamepadJoystick(controllerPtr); + return SDL_GetJoystickID(joystick); + } + + /** + * @return player index if set and supported, -1 otherwise + */ + public int getPlayerIndex() throws ControllerUnpluggedException { + ensureConnected(); + return SDL_GetGamepadPlayerIndex(controllerPtr); + } + + /** + * Sets player index. At the time being, this doesn't seem to change the indication lights on + * a controller on Windows, Linux and Mac, but only an internal representation index. + * @param index index to set + */ + public void setPlayerIndex(int index) throws ControllerUnpluggedException { + ensureConnected(); + SDL_SetGamepadPlayerIndex(controllerPtr, index); + } + + /** + * @return current power level of game controller, see {@link ControllerPowerLevel} enum values + * @throws ControllerUnpluggedException If the controller is not connected + */ + public ControllerPowerLevel getPowerLevel() throws ControllerUnpluggedException { + ensureConnected(); + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer powerLevel = stack.mallocInt(1); + + long joystick = SDL_GetGamepadJoystick(controllerPtr); + PowerState powerState = PowerState.getById(SDL_GetJoystickPowerInfo(joystick, powerLevel)); + int level = powerLevel.get(0); + + switch (powerState) { + case ERROR: + case UNKNOWN: + return ControllerPowerLevel.POWER_UNKNOWN; + case NO_BATTERY: + case CHARGING: + case CHARGED: + return ControllerPowerLevel.POWER_WIRED; + } + + if (level == -1) + return ControllerPowerLevel.POWER_UNKNOWN; + else if (level <= 5) + return ControllerPowerLevel.POWER_EMPTY; + else if (level <= 20) + return ControllerPowerLevel.POWER_LOW; + else if (level <= 70) + return ControllerPowerLevel.POWER_MEDIUM; + else + return ControllerPowerLevel.POWER_FULL; + } + } + + /** + * Convenience method to throw an exception if the controller is not connected. + */ + private void ensureConnected() throws ControllerUnpluggedException { + if(!isConnected()) { + throw new ControllerUnpluggedException("Controller at index " + index + " is not connected!"); + } + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerState.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerState.java new file mode 100644 index 0000000..b1983dc --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerState.java @@ -0,0 +1,412 @@ +package com.badlogic.gdx.controllers.desktop.support; + +/** + * This class represents the state of a gamepad at a given moment. This includes + * the state of the axes and the buttons. + * + * This is probably how most people should deal with gamepads. + * + * The isConnected field is pretty important. This is how you determine if the controller + * you asked for is plugged in. If isConnected is false, all other fields will be zero or false. + * For some applications, you might not need to even bother checking isConnected. + * + * All fields are public, but immutable. + * + * @author William Hartman + */ +public final class ControllerState { + private static final ControllerState DISCONNECTED_CONTROLLER = new ControllerState(); + + /** + * Whether or not the controller is currently connected. + * + * If the controller is disconnected, all other fields will be 0 or false. + */ + public final boolean isConnected; + + /** + * A string describing the type of controller (i.e. "PS4 Controller" or "XInput Controller") + */ + public final String controllerType; + + /** + * The x position of the left stick between -1 and 1 + */ + public final float leftStickX; + + /** + * The y position of the left stick between -1 and 1 + */ + public final float leftStickY; + + /** + * The x position of the right stick between -1 and 1 + */ + public final float rightStickX; + + /** + * The y position of the right stick between -1 and 1 + */ + public final float rightStickY; + + /** + * The angle of the left stick (for reference, 0 is right, 90 is up, 180 is left, 270 is down) + */ + public final float leftStickAngle; + + /** + * The amount the left stick is pushed in the current direction. This probably between 0 and 1, + * but this can't be guaranteed due to weird gamepads (like the square holes on a Logitech Dual Action) + */ + public final float leftStickMagnitude; + + /** + * The angle of the right stick (for reference, 0 is right, 90 is up, 180 is left, 270 is down) + */ + public final float rightStickAngle; + + /** + * The amount the right stick is pushed in the current direction. This probably between 0 and 1, + * but this can't be guaranteed due to weird gamepads (like the square holes on a Logitech Dual Action) + */ + public final float rightStickMagnitude; + + /** + * Whether or not the left stick is clicked in + */ + public final boolean leftStickClick; + + /** + * Whether or not the right stick is clicked in + */ + public final boolean rightStickClick; + + /** + * The position of the left trigger between 0 and 1 + */ + public final float leftTrigger; + + /** + * The position of the right trigger between 0 and 1 + */ + public final float rightTrigger; + + /** + * Whether or not the left stick was just is clicked in + */ + public final boolean leftStickJustClicked; + + /** + * Whether or not the right stick was just is clicked in + */ + public final boolean rightStickJustClicked; + + /** + * Whether or not the a button is pressed + */ + public final boolean a; + + /** + * Whether or not the b button is pressed + */ + public final boolean b; + + /** + * Whether or not the x button is pressed + */ + public final boolean x; + + /** + * Whether or not the y button is pressed + */ + public final boolean y; + + /** + * Whether or not the left bumper is pressed + */ + public final boolean lb; + + /** + * Whether or not the right bumper is pressed + */ + public final boolean rb; + + /** + * Whether or not the start button is pressed + */ + public final boolean start; + + /** + * Whether or not the back button is pressed + */ + public final boolean back; + + /** + * Whether or not the guide button is pressed. For some controller/platform combinations this + * doesn't work. You probably shouldn't use this. + */ + public final boolean guide; + + /** + * Whether or not the up button on the dpad is pushed + */ + public final boolean dpadUp; + + /** + * Whether or not the down button on the dpad is pushed + */ + public final boolean dpadDown; + + /** + * Whether or not the left button on the dpad is pushed + */ + public final boolean dpadLeft; + + /** + * Whether or not the right button on the dpad is pushed + */ + public final boolean dpadRight; + + /** + * Whether or not the a button was just pressed + */ + public final boolean aJustPressed; + + + /** + * Whether or not the b button was just pressed + */ + public final boolean bJustPressed; + + /** + * Whether or not the x button was just pressed + */ + public final boolean xJustPressed; + + /** + * Whether or not the y button was just pressed + */ + public final boolean yJustPressed; + + /** + * Whether or not the left bumper was just pressed + */ + public final boolean lbJustPressed; + + /** + * Whether or not the right bumper was just pressed + */ + public final boolean rbJustPressed; + + /** + * Whether or not the start button was just pressed + */ + public final boolean startJustPressed; + + /** + * Whether or not the back button was just pressed + */ + public final boolean backJustPressed; + + /** + * Whether or not the guide button was just pressed + */ + public final boolean guideJustPressed; + + /** + * Whether or not the up button on the dpad was just pressed + */ + public final boolean dpadUpJustPressed; + + /** + * Whether or not the down button on the dpad was just pressed + */ + public final boolean dpadDownJustPressed; + + /** + * Whether or not the left button on the dpad was just pressed + */ + public final boolean dpadLeftJustPressed; + + /** + * Whether or not the right button on the dpad was just pressed + */ + public final boolean dpadRightJustPressed; + + /** + * Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button + */ + public final boolean misc1; + public final boolean misc1JustPressed; + /** + * Xbox Elite paddle P1 + */ + public final boolean paddle1; + public final boolean paddle1JustPressed; + /** + * Xbox Elite paddle P3 + */ + public final boolean paddle2; + public final boolean paddle2JustPressed; + /** + * Xbox Elite paddle P2 + */ + public final boolean paddle3; + public final boolean paddle3JustPressed; + /** + * Xbox Elite paddle P4 + */ + public final boolean paddle4; + public final boolean paddle4JustPressed; + /** + * PS4/PS5 touchpad button + */ + public final boolean touchpadButton; + public final boolean touchpadButtonJustPressed; + + /** + * Return a controller state based on the current state of the passed controller. + * + * If the controller a disconnected mid-read, the disconnected controller is returned, and the + * pre-disconnection read data is ignored. + * + * @param c The ControllerIndex object whose state should be read. + */ + static ControllerState getInstanceFromController(ControllerIndex c) { + try { + return new ControllerState(c); + } catch (ControllerUnpluggedException e) { + return DISCONNECTED_CONTROLLER; + } + } + + /** + * Return a ControllerState that represents a disconnected controller. This object is shared. + * + * @return The ControllerState representing the disconnected controller. + */ + static ControllerState getDisconnectedControllerInstance() { + return DISCONNECTED_CONTROLLER; + } + + private ControllerState(ControllerIndex c) throws ControllerUnpluggedException { + isConnected = true; + controllerType = c.getName(); + leftStickX = c.getAxisState(ControllerAxis.LEFTX); + leftStickY = c.getAxisState(ControllerAxis.LEFTY); + rightStickX = c.getAxisState(ControllerAxis.RIGHTX); + rightStickY = c.getAxisState(ControllerAxis.RIGHTY); + leftStickAngle = (float) Math.toDegrees(Math.atan2(leftStickY, leftStickX)); + leftStickMagnitude = (float) Math.sqrt((leftStickX * leftStickX) + (leftStickY * leftStickY)); + rightStickAngle = (float) Math.toDegrees(Math.atan2(rightStickY, rightStickX)); + rightStickMagnitude = (float) Math.sqrt((rightStickX * rightStickX) + (rightStickY * rightStickY)); + leftTrigger = c.getAxisState(ControllerAxis.LEFT_TRIGGER); + rightTrigger = c.getAxisState(ControllerAxis.RIGHT_TRIGGER); + + leftStickJustClicked = c.isButtonJustPressed(ControllerButton.LEFT_STICK); + rightStickJustClicked = c.isButtonJustPressed(ControllerButton.RIGHT_STICK); + leftStickClick = c.isButtonPressed(ControllerButton.LEFT_STICK); + rightStickClick = c.isButtonPressed(ControllerButton.RIGHT_STICK); + + aJustPressed = c.isButtonJustPressed(ControllerButton.SOUTH); + bJustPressed = c.isButtonJustPressed(ControllerButton.EAST); + xJustPressed = c.isButtonJustPressed(ControllerButton.WEST); + yJustPressed = c.isButtonJustPressed(ControllerButton.NORTH); + lbJustPressed = c.isButtonJustPressed(ControllerButton.LEFT_SHOULDER); + rbJustPressed = c.isButtonJustPressed(ControllerButton.RIGHT_SHOULDER); + startJustPressed = c.isButtonJustPressed(ControllerButton.START); + backJustPressed = c.isButtonJustPressed(ControllerButton.BACK); + guideJustPressed = c.isButtonJustPressed(ControllerButton.GUIDE); + dpadUpJustPressed = c.isButtonJustPressed(ControllerButton.DPAD_UP); + dpadDownJustPressed = c.isButtonJustPressed(ControllerButton.DPAD_DOWN); + dpadLeftJustPressed = c.isButtonJustPressed(ControllerButton.DPAD_LEFT); + dpadRightJustPressed = c.isButtonJustPressed(ControllerButton.DPAD_RIGHT); + misc1JustPressed = c.isButtonJustPressed(ControllerButton.MISC1); + paddle1JustPressed = c.isButtonJustPressed(ControllerButton.RIGHT_PADDLE1); + paddle2JustPressed = c.isButtonJustPressed(ControllerButton.LEFT_PADDLE1); + paddle3JustPressed = c.isButtonJustPressed(ControllerButton.RIGHT_PADDLE2); + paddle4JustPressed = c.isButtonJustPressed(ControllerButton.LEFT_PADDLE2); + touchpadButtonJustPressed = c.isButtonJustPressed(ControllerButton.TOUCHPAD); + + a = c.isButtonPressed(ControllerButton.SOUTH); + b = c.isButtonPressed(ControllerButton.EAST); + x = c.isButtonPressed(ControllerButton.WEST); + y = c.isButtonPressed(ControllerButton.NORTH); + lb = c.isButtonPressed(ControllerButton.LEFT_SHOULDER); + rb = c.isButtonPressed(ControllerButton.RIGHT_SHOULDER); + start = c.isButtonPressed(ControllerButton.START); + back = c.isButtonPressed(ControllerButton.BACK); + guide = c.isButtonPressed(ControllerButton.GUIDE); + dpadUp = c.isButtonPressed(ControllerButton.DPAD_UP); + dpadDown = c.isButtonPressed(ControllerButton.DPAD_DOWN); + dpadLeft = c.isButtonPressed(ControllerButton.DPAD_LEFT); + dpadRight = c.isButtonPressed(ControllerButton.DPAD_RIGHT); + misc1 = c.isButtonPressed(ControllerButton.MISC1); + paddle1 = c.isButtonPressed(ControllerButton.RIGHT_PADDLE1); + paddle2 = c.isButtonPressed(ControllerButton.LEFT_PADDLE1); + paddle3 = c.isButtonPressed(ControllerButton.RIGHT_PADDLE2); + paddle4 = c.isButtonPressed(ControllerButton.LEFT_PADDLE2); + touchpadButton = c.isButtonPressed(ControllerButton.TOUCHPAD); + } + + private ControllerState() { + isConnected = false; + controllerType = "Not Connected"; + leftStickX = 0; + leftStickY = 0; + rightStickX = 0; + rightStickY = 0; + leftStickAngle = 0; + leftStickMagnitude = 0; + rightStickAngle = 0; + rightStickMagnitude = 0; + leftTrigger = 0; + rightTrigger = 0; + + leftStickJustClicked = false; + rightStickJustClicked = false; + leftStickClick = false; + rightStickClick = false; + + aJustPressed = false; + bJustPressed = false; + xJustPressed = false; + yJustPressed = false; + lbJustPressed = false; + rbJustPressed = false; + startJustPressed = false; + backJustPressed = false; + guideJustPressed = false; + dpadUpJustPressed = false; + dpadDownJustPressed = false; + dpadLeftJustPressed = false; + dpadRightJustPressed = false; + misc1JustPressed = false; + paddle1JustPressed = false; + paddle2JustPressed = false; + paddle3JustPressed = false; + paddle4JustPressed = false; + touchpadButtonJustPressed = false; + + a = false; + b = false; + x = false; + y = false; + lb = false; + rb = false; + start = false; + back = false; + guide = false; + dpadUp = false; + dpadDown = false; + dpadLeft = false; + dpadRight = false; + misc1 = false; + paddle1 = false; + paddle2 = false; + paddle3 = false; + paddle4 = false; + touchpadButton = false; + } +} + diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerUnpluggedException.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerUnpluggedException.java new file mode 100644 index 0000000..010f9b6 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/ControllerUnpluggedException.java @@ -0,0 +1,28 @@ +package com.badlogic.gdx.controllers.desktop.support; + +/** + * Signals that you are trying to access a gamepad that is not plugged in. + * + * @author William Hartman + */ +public class ControllerUnpluggedException extends Exception { + /** + * Constructs a {@code ControllerUnpluggedException} with {@code null} + * as its error detail message. + */ + public ControllerUnpluggedException() { + super(); + } + + /** + * Constructs a {@code ControllerUnpluggedException} with the specified detail message. + * + * @param message + * The detail message (which is saved for later retrieval + * by the {@link #getMessage()} method) + */ + public ControllerUnpluggedException(String message) { + super(message); + } +} + diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadController.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadController.java index d354467..78f0d8a 100644 --- a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadController.java +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadController.java @@ -5,36 +5,17 @@ import com.badlogic.gdx.controllers.ControllerMapping; import com.badlogic.gdx.controllers.ControllerPowerLevel; import com.badlogic.gdx.utils.IntFloatMap; -import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.IntIntMap; import com.badlogic.gdx.utils.Logger; import com.badlogic.gdx.utils.TimeUtils; -import com.studiohartman.jamepad.ControllerAxis; -import com.studiohartman.jamepad.ControllerButton; -import com.studiohartman.jamepad.ControllerIndex; -import com.studiohartman.jamepad.ControllerUnpluggedException; import java.util.UUID; public class JamepadController implements Controller { - private static final IntMap CODE_TO_BUTTON = new IntMap<>(ControllerButton.values().length); - private static final IntMap CODE_TO_AXIS = new IntMap<>(ControllerAxis.values().length); private static final Logger logger = new Logger(JamepadController.class.getSimpleName()); - // ControllerButton.values() and ControllerAxis.values() is cached once, to avoid producing garbage every frame - private static final ControllerButton[] CONTROLLER_BUTTON_VALUES = ControllerButton.values(); - private static final ControllerAxis[] CONTROLLER_AXIS_VALUES = ControllerAxis.values(); - - static { - for (ControllerButton button : CONTROLLER_BUTTON_VALUES) { - CODE_TO_BUTTON.put(button.ordinal(), button); - } - - for (ControllerAxis axis : CONTROLLER_AXIS_VALUES) { - CODE_TO_AXIS.put(axis.ordinal(), axis); - } - } private final CompositeControllerListener compositeControllerListener = new CompositeControllerListener(); - private final IntMap buttonState = new IntMap<>(); + private final IntIntMap buttonState = new IntIntMap(); // there is no IntBoolMap, so we do "1" = true, "0" = false private final IntFloatMap axisState = new IntFloatMap(); private final String uuid; private final String name; @@ -55,7 +36,7 @@ public JamepadController(ControllerIndex controllerIndex) { @Override public boolean getButton(final int buttonCode) { try { - ControllerButton button = toButton(buttonCode); + ControllerButton button = ControllerButton.getById(buttonCode); return button != null && controllerIndex.isButtonPressed(button); } catch (ControllerUnpluggedException | NullPointerException e) { setDisconnected(); @@ -66,7 +47,7 @@ public boolean getButton(final int buttonCode) { @Override public float getAxis(final int axisCode) { try { - ControllerAxis axis = toAxis(axisCode); + ControllerAxis axis = ControllerAxis.getById(axisCode); if (axis == null) { return 0.0f; @@ -123,22 +104,16 @@ public boolean update() { return connected; } - private ControllerButton toButton(int buttonCode) { - return CODE_TO_BUTTON.get(buttonCode); - } - - private ControllerAxis toAxis(int axisCode) { - return CODE_TO_AXIS.get(axisCode); - } - private void updateAxisState() { - for (ControllerAxis axis : CONTROLLER_AXIS_VALUES) { - int id = axis.ordinal(); + for (ControllerAxis axis : ControllerAxis.VALUES) { + if (axis == ControllerAxis.INVALID) + continue; + int id = axis.getId(); float value = getAxis(id); - if (value != axisState.get(id, 0)) { + if (value != axisState.get(id, Float.NaN)) { if (logger.getLevel() == Logger.DEBUG) { - logger.debug("Axis [" + id + " - " + toAxis(id) + "] moved [" + value + "]"); + logger.debug("Axis [" + id + " - " + ControllerAxis.getById(id) + "] moved [" + value + "]"); } compositeControllerListener.axisMoved(this, id, value); } @@ -147,11 +122,14 @@ private void updateAxisState() { } private void updateButtonsState() { - for (ControllerButton button : CONTROLLER_BUTTON_VALUES) { - int id = button.ordinal(); + for (ControllerButton button : ControllerButton.VALUES) { + if (button == ControllerButton.INVALID) + continue; + int id = button.getId(); boolean pressed = getButton(id); - if (pressed != buttonState.get(id)) { + boolean oldState = buttonState.get(id, -1) == 1; + if (pressed != oldState) { if (pressed) { compositeControllerListener.buttonDown(this, id); } else { @@ -159,20 +137,20 @@ private void updateButtonsState() { } if (logger.getLevel() == Logger.DEBUG) { - logger.debug("Button [" + id + " - " + toButton(id) + "] is " + (pressed ? "pressed" : "released")); + logger.debug("Button [" + id + " - " + ControllerButton.getById(id) + "] is " + (pressed ? "pressed" : "released")); } } - buttonState.put(id, pressed); + buttonState.put(id, pressed ? 1 : 0); } } private void initializeState() { - for (ControllerAxis axis : CONTROLLER_AXIS_VALUES) { - axisState.put(axis.ordinal(), 0); + for (ControllerAxis axis : ControllerAxis.VALUES) { + axisState.put(axis.getId(), 0.0f); } - for (ControllerButton button : CONTROLLER_BUTTON_VALUES) { - buttonState.put(button.ordinal(), false); + for (ControllerButton button : ControllerButton.VALUES) { + buttonState.put(button.getId(), 0); } } @@ -254,9 +232,10 @@ public int getMaxButtonIndex() { return maxButtonIndex; } - maxButtonIndex = CODE_TO_BUTTON.size - 1; + // TODO: 04.06.2025 I don't think this code makes any sense, but I might be mistaken + maxButtonIndex = ControllerButton.VALUES.length - 1; try { - while (maxButtonIndex > 0 && !controllerIndex.isButtonAvailable(CODE_TO_BUTTON.get(maxButtonIndex))) { + while (maxButtonIndex > 0 && !controllerIndex.isButtonAvailable(ControllerButton.VALUES[maxButtonIndex])) { maxButtonIndex--; } } catch (ControllerUnpluggedException | NullPointerException e) { @@ -272,12 +251,17 @@ public int getAxisCount() { return axisCount; } - axisCount = CODE_TO_AXIS.size; + axisCount = 0; try { - while (axisCount > 0 && !controllerIndex.isAxisAvailable(CODE_TO_AXIS.get(axisCount - 1))) { - axisCount--; + for (ControllerAxis axis : ControllerAxis.VALUES) { + if (axis == ControllerAxis.INVALID) + continue; + + if (controllerIndex.isAxisAvailable(axis)) + axisCount++; + } - } catch (ControllerUnpluggedException | NullPointerException e) { + } catch (ControllerUnpluggedException e) { setDisconnected(); } @@ -297,22 +281,8 @@ public ControllerMapping getMapping() { @Override public ControllerPowerLevel getPowerLevel() { try { - switch (controllerIndex.getPowerLevel()) { - case POWER_MAX: - case POWER_FULL: - return ControllerPowerLevel.POWER_FULL; - case POWER_MEDIUM: - return ControllerPowerLevel.POWER_MEDIUM; - case POWER_LOW: - return ControllerPowerLevel.POWER_LOW; - case POWER_EMPTY: - return ControllerPowerLevel.POWER_EMPTY; - case POWER_WIRED: - return ControllerPowerLevel.POWER_WIRED; - default: - return ControllerPowerLevel.POWER_UNKNOWN; - } - } catch (Throwable t) { + return controllerIndex.getPowerLevel(); + } catch (ControllerUnpluggedException e) { return ControllerPowerLevel.POWER_UNKNOWN; } } diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadControllerMonitor.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadControllerMonitor.java index 4694e81..c3b7330 100644 --- a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadControllerMonitor.java +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadControllerMonitor.java @@ -5,19 +5,16 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.controllers.desktop.JamepadControllerManager; -import com.studiohartman.jamepad.ControllerIndex; -import com.studiohartman.jamepad.ControllerManager; -import com.studiohartman.jamepad.ControllerUnpluggedException; public class JamepadControllerMonitor implements Runnable { - private final ControllerManager controllerManager; + private final SDLControllerManager controllerManager; private final ControllerListener listener; private final IntMap indexToController = new IntMap<>(JamepadControllerManager.jamepadConfiguration.maxNumControllers); // temporary array for delaying connect messages private final Array connectedControllers = new Array(); - public JamepadControllerMonitor(ControllerManager controllerManager, ControllerListener listener) { + public JamepadControllerMonitor(SDLControllerManager controllerManager, ControllerListener listener) { this.controllerManager = controllerManager; this.listener = listener; diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadMapping.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadMapping.java index 4a62a6e..b5f70f7 100644 --- a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadMapping.java +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadMapping.java @@ -1,23 +1,21 @@ package com.badlogic.gdx.controllers.desktop.support; import com.badlogic.gdx.controllers.ControllerMapping; -import com.studiohartman.jamepad.ControllerAxis; -import com.studiohartman.jamepad.ControllerButton; public class JamepadMapping extends ControllerMapping { private static JamepadMapping instance; JamepadMapping() { - super(ControllerAxis.LEFTX.ordinal(), ControllerAxis.LEFTY.ordinal(), - ControllerAxis.RIGHTX.ordinal(), ControllerAxis.RIGHTY.ordinal(), - ControllerButton.A.ordinal(), ControllerButton.B.ordinal(), - ControllerButton.X.ordinal(), ControllerButton.Y.ordinal(), - ControllerButton.BACK.ordinal(), ControllerButton.START.ordinal(), - ControllerButton.LEFTBUMPER.ordinal(), UNDEFINED, - ControllerButton.RIGHTBUMPER.ordinal(), UNDEFINED, - ControllerButton.LEFTSTICK.ordinal(), ControllerButton.RIGHTSTICK.ordinal(), - ControllerButton.DPAD_UP.ordinal(), ControllerButton.DPAD_DOWN.ordinal(), - ControllerButton.DPAD_LEFT.ordinal(), ControllerButton.DPAD_RIGHT.ordinal()); + super(ControllerAxis.LEFTX.getId(), ControllerAxis.LEFTY.getId(), + ControllerAxis.RIGHTX.getId(), ControllerAxis.RIGHTY.getId(), + ControllerButton.SOUTH.getId(), ControllerButton.EAST.getId(), + ControllerButton.WEST.getId(), ControllerButton.NORTH.getId(), + ControllerButton.BACK.getId(), ControllerButton.START.getId(), + ControllerButton.LEFT_SHOULDER.getId(), UNDEFINED, + ControllerButton.RIGHT_SHOULDER.getId(), UNDEFINED, + ControllerButton.LEFT_STICK.getId(), ControllerButton.LEFT_SHOULDER.getId(), + ControllerButton.DPAD_UP.getId(), ControllerButton.DPAD_DOWN.getId(), + ControllerButton.DPAD_LEFT.getId(), ControllerButton.DPAD_RIGHT.getId()); } static JamepadMapping getInstance() { diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadShutdownHook.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadShutdownHook.java index 9d930e5..6e99589 100644 --- a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadShutdownHook.java +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JamepadShutdownHook.java @@ -1,12 +1,11 @@ package com.badlogic.gdx.controllers.desktop.support; import com.badlogic.gdx.LifecycleListener; -import com.studiohartman.jamepad.ControllerManager; public class JamepadShutdownHook implements LifecycleListener { - private final ControllerManager controllerManager; + private final SDLControllerManager controllerManager; - public JamepadShutdownHook(ControllerManager controllerManager) { + public JamepadShutdownHook(SDLControllerManager controllerManager) { this.controllerManager = controllerManager; } diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JoystickConnectionState.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JoystickConnectionState.java new file mode 100644 index 0000000..3e74da8 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/JoystickConnectionState.java @@ -0,0 +1,37 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import org.lwjgl.sdl.SDLJoystick; + +/** + * Possible connection states for a joystick device. + * + * This is used by SDL_GetJoystickConnectionState to report how a device is + * connected to the system. + */ +public enum JoystickConnectionState { + INVALID(SDLJoystick.SDL_JOYSTICK_CONNECTION_INVALID), + UNKNOWN(SDLJoystick.SDL_JOYSTICK_CONNECTION_UNKNOWN), + WIRED(SDLJoystick.SDL_JOYSTICK_CONNECTION_WIRED), + WIRELESS(SDLJoystick.SDL_JOYSTICK_CONNECTION_WIRELESS),; + + public static final JoystickConnectionState[] VALUES = values(); + + private final int id; + + JoystickConnectionState(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static JoystickConnectionState getById(int id) { + for (JoystickConnectionState state : VALUES) { + if (state.id == id) + return state; + } + + throw new IllegalArgumentException("No such joystick connection state with id " + id); + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/PowerState.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/PowerState.java new file mode 100644 index 0000000..ad08bcc --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/PowerState.java @@ -0,0 +1,38 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import org.lwjgl.sdl.SDLPower; + +/** + * The basic state for the system's power supply. + * + * These are results returned by SDL_GetPowerInfo(). + */ +public enum PowerState { + ERROR(SDLPower.SDL_POWERSTATE_ERROR), /** error determining power status */ + UNKNOWN(SDLPower.SDL_POWERSTATE_UNKNOWN), /** cannot determine power status */ + ON_BATTERY(SDLPower.SDL_POWERSTATE_ON_BATTERY), /** Not plugged in, running on the battery */ + NO_BATTERY(SDLPower.SDL_POWERSTATE_NO_BATTERY), /** Plugged in, no battery available */ + CHARGING(SDLPower.SDL_POWERSTATE_CHARGING), /** Plugged in, charging battery */ + CHARGED(SDLPower.SDL_POWERSTATE_CHARGED),; /** Plugged in, battery charged */ + + public static final PowerState[] VALUES = values(); + + private final int id; + + PowerState(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static PowerState getById(int id) { + for (PowerState state : VALUES) { + if (state.id == id) + return state; + } + + throw new IllegalArgumentException("No such power state with id " + id); + } +} diff --git a/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/SDLControllerManager.java b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/SDLControllerManager.java new file mode 100644 index 0000000..d8f01b8 --- /dev/null +++ b/gdx-controllers-desktop/src/main/java/com/badlogic/gdx/controllers/desktop/support/SDLControllerManager.java @@ -0,0 +1,331 @@ +package com.badlogic.gdx.controllers.desktop.support; + +import com.badlogic.gdx.controllers.desktop.Configuration; + +import org.lwjgl.BufferUtils; +import org.lwjgl.sdl.SDL_Event; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import static org.lwjgl.sdl.SDLHints.*; +import static org.lwjgl.sdl.SDLInit.*; +import static org.lwjgl.sdl.SDLEvents.*; +import static org.lwjgl.sdl.SDLGamepad.*; +import static org.lwjgl.sdl.SDLJoystick.*; +import static org.lwjgl.sdl.SDLError.*; +import static org.lwjgl.sdl.SDLIOStream.*; + +/** + * This class handles initializing the native library, connecting to controllers, and managing the + * list of controllers. + * + * Generally, after creating a ControllerManager object and calling initSDLGamepad() on it, you + * would access the states of the attached gamepads by calling getState(). + * + * For some applications (but probably very few), getState may have a performance impact. In this + * case, it may make sense to use the getControllerIndex() method to access the objects used + * internally by ControllerManager. + * + * @author William Hartman + */ +public class SDLControllerManager { + + private final SDL_Event sdlEvent = SDL_Event.create(); // Shared temp obj + private final Configuration configuration; + private String mappingsPath; + private boolean isInitialized; + private ControllerIndex[] controllers; + + /** + * Default constructor. Makes a manager for 4 controllers with the built in mappings from here: + * https://github.com/gabomdq/SDL_GameControllerDB + */ + public SDLControllerManager() { + this(new Configuration(), "/gamecontrollerdb.txt"); + } + + /** + * Constructor. Uses built-in mappings from here: https://github.com/gabomdq/SDL_GameControllerDB + * + * @param configuration see {@link Configuration and its fields} + */ + public SDLControllerManager(Configuration configuration) { + this(configuration, "/gamecontrollerdb.txt"); + } + + /** + * Constructor. + * + * @param mappingsPath The path to a file containing SDL controller mappings. + * @param configuration see {@link Configuration and its fields} + */ + public SDLControllerManager(Configuration configuration, String mappingsPath) { + this.configuration = configuration; + this.mappingsPath = mappingsPath; + isInitialized = false; + controllers = new ControllerIndex[configuration.maxNumControllers]; + } + + /** + * Initialize the ControllerIndex library. This loads the native library and initializes SDL + * in the native code. + * + * @throws IllegalStateException If the native code fails to initialize or if SDL is already initialized + */ + public void initSDLGamepad() throws IllegalStateException { + if(isInitialized) { + throw new IllegalStateException("SDL is already initialized!"); + } + + if (!configuration.useRawInput) + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + if (!SDL_Init(SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) + throw new IllegalStateException("SDL init failed!"); + + //We don't want any controller connections events (which are automatically generated at init) + //since they interfere with us detecting new controllers, so we go through all events and clear them. + while (SDL_PollEvent(sdlEvent)); + + isInitialized = true; + + //Set controller mappings. The possible exception is caught, since stuff will still work ok + //for most people if mapping aren't set. + try { + addMappingsFromFile(mappingsPath); + } catch (IOException | IllegalStateException e) { + System.err.println("Failed to load mapping with original location \"" + mappingsPath + "\", " + + "Falling back of SDL's built in mappings"); + e.printStackTrace(); + } + + //Connect and keep track of the controllers + for(int i = 0; i < controllers.length; i++) { + controllers[i] = new ControllerIndex(i); + } + } + + /** + * This method quits all the native stuff. Call it when you're done with Jamepad. + */ + public void quitSDLGamepad() { + for(ControllerIndex c: controllers) { + c.close(); + } + SDL_Quit(); + controllers = new ControllerIndex[0]; + isInitialized = false; + } + + /** + * Return the state of a controller at the passed index. This is probably the way most people + * should use this library. It's simpler and less verbose, and controller connections and + * disconnections are automatically handled. + * + * Also, no exceptions are thrown here (unless Jamepad isn't initialized), so you don't need + * to have a million try/catches or anything. + * + * The returned state is immutable. This means an object is allocated every time you call this + * (unless the controller is disconnected). This shouldn't be a big deal (even for games) if your + * GC is tuned well, but if this is a problem for you, you can go directly through the internal + * ControllerIndex objects using getControllerIndex(). + * + * update() is called each time this method is called. Buttons are also queried, so values + * returned from isButtonJustPressed() in ControllerIndex may not be what you expect. Calling + * this method will have side effects if you are using the ControllerIndex objects yourself. + * This should be fine unless you are mixing and matching this method with ControllerIndex + * objects, which you probably shouldn't do anyway. + * + * @param index The index of the controller to be checked + * @return The state of the controller at the passed index. + * @throws IllegalStateException if Jamepad was not initialized + */ + public ControllerState getState(int index) throws IllegalStateException { + verifyInitialized(); + + if(index < controllers.length && index >= 0) { + update(); + return ControllerState.getInstanceFromController(controllers[index]); + } else { + return ControllerState.getDisconnectedControllerInstance(); + } + } + + /** + * Starts vibrating the controller at this given index. If this fails for one reason or another (e.g. + * the controller at that index doesn't support haptics, or if there is no controller at that index), + * this method will return false. + * + * Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling. + * + * @param index The index of the controller that will be vibrated + * @param leftMagnitude The intensity of the left rumble motor (0-1) + * @param rightMagnitude The intensity of the rught rumble motor (0-1) + * @return Whether or not vibration was successfully started + * @throws IllegalStateException if Jamepad was not initialized + */ + public boolean doVibration(int index, float leftMagnitude, float rightMagnitude, int duration_ms) throws IllegalStateException { + verifyInitialized(); + + if(index < controllers.length && index >= 0) { + try { + return controllers[index].doVibration(leftMagnitude, rightMagnitude, duration_ms); + } catch (ControllerUnpluggedException e) { + return false; + } + } + + return false; + } + + /** + * Returns a the ControllerIndex object with the passed index (0 for p1, 1 for p2, etc.). + * + * You should only use this method if you're worried about the object allocations from getState(). + * If you decide to do things this way, your code will be a good bit more verbose and you'll + * need to deal with potential exceptions. + * + * It is generally safe to store objects returned from this method. They will only change internally + * if you call quitSDLGamepad() followed by a call to initSDLGamepad(). + * + * Calling update() will run through all the controllers to check for newly plugged in or unplugged + * controllers. You could do this from your code, but keep that in mind. + * + * @param index the index of the ControllerIndex that will be returned + * @return The internal ControllerIndex object for the passed index. + * @throws IllegalStateException if Jamepad was not initialized + */ + public ControllerIndex getControllerIndex(int index) { + verifyInitialized(); + return controllers[index]; + } + + /** + * Return the number of controllers that are actually connected. This may disagree with + * the ControllerIndex objects held in here if something has been plugged in or unplugged + * since update() was last called. + * + * @return the number of connected controllers. + * @throws IllegalStateException if Jamepad was not initialized + */ + public int getNumControllers() { + verifyInitialized(); + IntBuffer joysticks = SDL_GetJoysticks(); + if (joysticks == null) + return 0; + + int numGamepads = 0; + for (int i = 0; i < joysticks.remaining(); i++) { + int joystickId = joysticks.get(i); + if(SDL_IsGamepad(joystickId)) + numGamepads++; + + } + + return numGamepads; + } + + /** + * Refresh the connected controllers in the controller list if something has been connected or + * unplugged. + * + * If there hasn't been a change in whether controller are connected or not, nothing will happen. + * + * @return True if the controller list was refreshed, false otherwise + * @throws IllegalStateException if Jamepad was not initialized + */ + public boolean update() { + verifyInitialized(); + SDL_UpdateJoysticks(); + while (SDL_PollEvent(sdlEvent)) { + if (sdlEvent.type() == SDL_EVENT_JOYSTICK_ADDED || sdlEvent.type() == SDL_EVENT_JOYSTICK_REMOVED) { + for (int i = 0; i < controllers.length; i++) { + controllers[i].reconnectController(); + } + return true; + } + } + return false; + } + + /** + * This method adds mappings held in the specified file. The file is copied to the temp folder so + * that it can be read by the native code (if running from a .jar for instance) + * + * @param path The path to the file containing controller mappings. + * @throws IOException if the file cannot be read, copied to a temp folder, or deleted. + * @throws IllegalStateException if the mappings cannot be applied to SDL + */ + public void addMappingsFromFile(String path) throws IOException, IllegalStateException { + InputStream source = getClass().getResourceAsStream(path); + if(source==null) source = ClassLoader.getSystemResourceAsStream(path); + if(source==null && new File(path).exists()) source = new FileInputStream(path); + if(source==null) throw new IOException("Cannot open resource from classpath "+path); + + if(configuration.loadDatabaseInMemory) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + int read; + byte[] data = new byte[4096]; + + while((read = source.read(data, 0, data.length)) != -1) { + out.write(data, 0, read); + } + source.close(); + + byte[] b = out.toByteArray(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); + buffer.put(data); + buffer.flip(); + + long stream = SDL_IOFromMem(buffer); + if (stream == 0) + throw new IllegalStateException("Failed to create SDLIOStream: " + getLastNativeError()); + + int error = SDL_AddGamepadMappingsFromIO(stream, true); + if (error < 0) + throw new IllegalStateException("Failed to load mappings from SDLIOStream, error: " + error + ", msg: " + getLastNativeError() + "! Falling back to build in SDL mappings."); + } + else { + /* + Copy the file to a temp folder. SDL can't read files held in .jars, and that's probably how + most people would use this library. + */ + Path extractedLoc = Files.createTempFile(null, null).toAbsolutePath(); + + Files.copy(source, extractedLoc, StandardCopyOption.REPLACE_EXISTING); + + int err = SDL_AddGamepadMappingsFromFile(extractedLoc.toString()); + if(err < 0) { + throw new IllegalStateException("Failed to load SDL controller mappings from" + extractedLoc + " with error-code " + err + " and message " + getLastNativeError() +"!" + + " Falling back to build in SDL mappings."); + } + + Files.delete(extractedLoc); + } + } + + /** + * @return last error message logged by the native lib. Use this for debugging purposes. + */ + public String getLastNativeError() { + return SDL_GetError(); + } + + private boolean verifyInitialized() throws IllegalStateException { + if(!isInitialized) { + throw new IllegalStateException("SDL_GameController is not initialized!"); + } + return true; + } +} + diff --git a/gradle.properties b/gradle.properties index d1ea09b..0080efb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ +android.useAndroidX=true org.gradle.daemon=true org.gradle.jvmargs=-Xms1024m -Xmx2048m org.gradle.configureondemand=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e589..002b867 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,22 +133,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..db3a6ac 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/test/android/AndroidManifest.xml b/test/android/AndroidManifest.xml index 369d154..550ca04 100644 --- a/test/android/AndroidManifest.xml +++ b/test/android/AndroidManifest.xml @@ -1,6 +1,5 @@ - + + android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout" + android:exported="true"> diff --git a/test/android/build.gradle b/test/android/build.gradle index 7e5064d..5e2d735 100644 --- a/test/android/build.gradle +++ b/test/android/build.gradle @@ -1,7 +1,8 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 30 + namespace "com.badlogic.gdx.controllers.test" + compileSdk 35 sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -30,8 +31,8 @@ android { } defaultConfig { applicationId 'com.badlogic.gdx.controllers.test' - minSdkVersion 19 - targetSdkVersion 30 + minSdkVersion 21 + targetSdkVersion 35 versionCode 1 versionName "1.0" } @@ -49,7 +50,6 @@ dependencies { implementation project(':gdx-controllers-android') implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" @@ -72,7 +72,6 @@ task copyAndroidNatives() { def outputDir = null if(jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a") if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") - if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi") if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64") if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") if(outputDir != null) { diff --git a/test/desktop/build.gradle b/test/desktop/build.gradle deleted file mode 100644 index 4eb551d..0000000 --- a/test/desktop/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -apply plugin: 'application' - -sourceSets.main.resources.srcDirs += [ project(':test').file('assets').path ] -mainClassName = 'com.badlogic.gdx.controllers.test.desktop.DesktopLauncher' -eclipse.project.name = appName + '-desktop' -sourceCompatibility = 8.0 - -dependencies { - implementation project(':test:core') - implementation project(':gdx-controllers-desktop') - implementation "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" - implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" -} - -jar { - archiveFileName = "${appName}-${archiveVersion.get()}.jar" - dependsOn configurations.runtimeClasspath - from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - manifest { - attributes 'Main-Class': project.mainClassName - } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -run { - ignoreExitValue = true -} diff --git a/test/desktop/src/main/java/com/badlogic/gdx/controllers/test/desktop/DesktopLauncher.java b/test/desktop/src/main/java/com/badlogic/gdx/controllers/test/desktop/DesktopLauncher.java deleted file mode 100644 index 4115cea..0000000 --- a/test/desktop/src/main/java/com/badlogic/gdx/controllers/test/desktop/DesktopLauncher.java +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************* - * Copyright 2014 See AUTHORS file. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ - -package com.badlogic.gdx.controllers.test.desktop; - -import com.badlogic.gdx.backends.lwjgl.LwjglApplication; -import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.badlogic.gdx.controllers.test.ControllersTest; - -/** Launches the desktop (LWJGL) application. */ -public class DesktopLauncher { - public static void main (String[] args) { - createApplication(); - } - - private static LwjglApplication createApplication () { - return new LwjglApplication(new ControllersTest(), getDefaultConfiguration()); - } - - private static LwjglApplicationConfiguration getDefaultConfiguration () { - LwjglApplicationConfiguration configuration = new LwjglApplicationConfiguration(); - configuration.title = "test"; - configuration.width = 640; - configuration.height = 480; - //// This prevents a confusing error that would appear after exiting normally. - configuration.forceExit = false; - - return configuration; - } -} diff --git a/test/ios/build.gradle b/test/ios/build.gradle index d2252ff..d3f6a24 100644 --- a/test/ios/build.gradle +++ b/test/ios/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.19' + classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.23' } } diff --git a/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/IOSLauncher.java b/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/IOSLauncher.java index f67ef57..44344df 100644 --- a/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/IOSLauncher.java +++ b/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/IOSLauncher.java @@ -20,6 +20,7 @@ import com.badlogic.gdx.backends.iosrobovm.IOSApplication; import com.badlogic.gdx.backends.iosrobovm.IOSApplicationConfiguration; import com.badlogic.gdx.backends.iosrobovm.IOSGraphics; +import com.badlogic.gdx.backends.iosrobovm.IOSUIViewController; import com.badlogic.gdx.controllers.IosControllerManager; import com.badlogic.gdx.controllers.test.ControllersTest; @@ -43,7 +44,7 @@ public void create() { }; return new IOSApplication(testApp, configuration) { @Override - protected IOSGraphics.IOSUIViewController createUIViewController(IOSGraphics graphics) { + protected IOSUIViewController createUIViewController(IOSGraphics graphics) { return new MyUIViewController(this, graphics); } }; diff --git a/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/MyUIViewController.java b/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/MyUIViewController.java index dfda4df..44c892d 100644 --- a/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/MyUIViewController.java +++ b/test/ios/src/main/java/com/badlogic/gdx/controllers/test/ios/MyUIViewController.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.backends.iosrobovm.IOSApplication; import com.badlogic.gdx.backends.iosrobovm.IOSGraphics; +import com.badlogic.gdx.backends.iosrobovm.IOSUIViewController; import com.badlogic.gdx.controllers.IosControllerManager; import org.robovm.apple.uikit.UIKeyCommand; @@ -11,7 +12,7 @@ import org.robovm.objc.annotation.TypeEncoding; import org.robovm.rt.bro.annotation.Callback; -public class MyUIViewController extends IOSGraphics.IOSUIViewController { +public class MyUIViewController extends IOSUIViewController { protected MyUIViewController(IOSApplication app, IOSGraphics graphics) { super(app, graphics); }