Mac and Linux SDL2 binary snapshots
Edward Rudd
2021-06-15 dec7875a6e23212021e4d9080330a42832dfe02a
source/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java
@@ -6,18 +6,24 @@
import java.util.List;
import android.content.Context;
import android.os.*;
import android.view.*;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
public class SDLControllerManager
public class SDLControllerManager
{
    public static native int nativeSetupJNI();
    public static native int nativeAddJoystick(int device_id, String name, String desc,
                                               int is_accelerometer, int nbuttons,
                                               int vendor_id, int product_id,
                                               boolean is_accelerometer, int button_mask,
                                               int naxes, int nhats, int nballs);
    public static native int nativeRemoveJoystick(int device_id);
    public static native int nativeAddHaptic(int device_id, String name);
@@ -35,21 +41,21 @@
    private static final String TAG = "SDLControllerManager";
    public static void initialize() {
        mJoystickHandler = null;
        mHapticHandler = null;
        SDLControllerManager.setup();
    }
    public static void setup() {
        if (Build.VERSION.SDK_INT >= 16) {
            mJoystickHandler = new SDLJoystickHandler_API16();
        } else if (Build.VERSION.SDK_INT >= 12) {
            mJoystickHandler = new SDLJoystickHandler_API12();
        } else {
            mJoystickHandler = new SDLJoystickHandler();
        if (mJoystickHandler == null) {
            if (Build.VERSION.SDK_INT >= 19) {
                mJoystickHandler = new SDLJoystickHandler_API19();
            } else {
                mJoystickHandler = new SDLJoystickHandler_API16();
            }
        }
        mHapticHandler = new SDLHapticHandler();
        if (mHapticHandler == null) {
            if (Build.VERSION.SDK_INT >= 26) {
                mHapticHandler = new SDLHapticHandler_API26();
            } else {
                mHapticHandler = new SDLHapticHandler();
            }
        }
    }
    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
@@ -74,8 +80,16 @@
    /**
     * This method is called by SDL using JNI.
     */
    public static void hapticRun(int device_id, int length) {
        mHapticHandler.run(device_id, length);
    public static void hapticRun(int device_id, float intensity, int length) {
        mHapticHandler.run(device_id, intensity, length);
    }
    /**
     * This method is called by SDL using JNI.
     */
    public static void hapticStop(int device_id)
    {
        mHapticHandler.stop(device_id);
    }
    // Check if a given device is considered a possible SDL joystick
@@ -89,9 +103,9 @@
        int sources = device.getSources();
        /* This is called for every button press, so let's not spam the logs */
        /**
        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) {
            Log.v(TAG, "Input device " + device.getName() + " is a joystick.");
        /*
        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
            Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
        }
        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
            Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
@@ -99,9 +113,9 @@
        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
            Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
        }
        **/
        */
        return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
        return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
        );
@@ -109,7 +123,6 @@
}
/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
class SDLJoystickHandler {
    /**
@@ -129,7 +142,7 @@
}
/* Actual joystick functionality available for API >= 12 devices */
class SDLJoystickHandler_API12 extends SDLJoystickHandler {
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
    static class SDLJoystick {
        public int device_id;
@@ -141,13 +154,27 @@
    static class RangeComparator implements Comparator<InputDevice.MotionRange> {
        @Override
        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
            return arg0.getAxis() - arg1.getAxis();
            // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
            int arg0Axis = arg0.getAxis();
            int arg1Axis = arg1.getAxis();
            if (arg0Axis == MotionEvent.AXIS_GAS) {
                arg0Axis = MotionEvent.AXIS_BRAKE;
            } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
                arg0Axis = MotionEvent.AXIS_GAS;
            }
            if (arg1Axis == MotionEvent.AXIS_GAS) {
                arg1Axis = MotionEvent.AXIS_BRAKE;
            } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
                arg1Axis = MotionEvent.AXIS_GAS;
            }
            return arg0Axis - arg1Axis;
        }
    }
    private ArrayList<SDLJoystick> mJoysticks;
    private final ArrayList<SDLJoystick> mJoysticks;
    public SDLJoystickHandler_API12() {
    public SDLJoystickHandler_API16() {
        mJoysticks = new ArrayList<SDLJoystick>();
    }
@@ -155,18 +182,14 @@
    @Override
    public void pollInputDevices() {
        int[] deviceIds = InputDevice.getDeviceIds();
        // It helps processing the device ids in reverse order
        // For example, in the case of the XBox 360 wireless dongle,
        // so the first controller seen by SDL matches what the receiver
        // considers to be the first controller
        for(int i=deviceIds.length-1; i>-1; i--) {
            SDLJoystick joystick = getJoystick(deviceIds[i]);
            if (joystick == null) {
                joystick = new SDLJoystick();
                InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
                if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
                    joystick.device_id = deviceIds[i];
        for (int device_id : deviceIds) {
            if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
                SDLJoystick joystick = getJoystick(device_id);
                if (joystick == null) {
                    InputDevice joystickDevice = InputDevice.getDevice(device_id);
                    joystick = new SDLJoystick();
                    joystick.device_id = device_id;
                    joystick.name = joystickDevice.getName();
                    joystick.desc = getJoystickDescriptor(joystickDevice);
                    joystick.axes = new ArrayList<InputDevice.MotionRange>();
@@ -174,54 +197,57 @@
                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
                    Collections.sort(ranges, new RangeComparator());
                    for (InputDevice.MotionRange range : ranges ) {
                    for (InputDevice.MotionRange range : ranges) {
                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
                            if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
                                range.getAxis() == MotionEvent.AXIS_HAT_Y) {
                            if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
                                joystick.hats.add(range);
                            }
                            else {
                            } else {
                                joystick.axes.add(range);
                            }
                        }
                    }
                    mJoysticks.add(joystick);
                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1,
                                                           joystick.axes.size(), joystick.hats.size()/2, 0);
                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
                            getVendorId(joystickDevice), getProductId(joystickDevice), false,
                            getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
                }
            }
        }
        /* Check removed devices */
        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
        for(int i=0; i < mJoysticks.size(); i++) {
            int device_id = mJoysticks.get(i).device_id;
            int j;
            for (j=0; j < deviceIds.length; j++) {
                if (device_id == deviceIds[j]) break;
        ArrayList<Integer> removedDevices = null;
        for (SDLJoystick joystick : mJoysticks) {
            int device_id = joystick.device_id;
            int i;
            for (i = 0; i < deviceIds.length; i++) {
                if (device_id == deviceIds[i]) break;
            }
            if (j == deviceIds.length) {
                removedDevices.add(Integer.valueOf(device_id));
            if (i == deviceIds.length) {
                if (removedDevices == null) {
                    removedDevices = new ArrayList<Integer>();
                }
                removedDevices.add(device_id);
            }
        }
        for(int i=0; i < removedDevices.size(); i++) {
            int device_id = removedDevices.get(i).intValue();
            SDLControllerManager.nativeRemoveJoystick(device_id);
            for (int j=0; j < mJoysticks.size(); j++) {
                if (mJoysticks.get(j).device_id == device_id) {
                    mJoysticks.remove(j);
                    break;
        if (removedDevices != null) {
            for (int device_id : removedDevices) {
                SDLControllerManager.nativeRemoveJoystick(device_id);
                for (int i = 0; i < mJoysticks.size(); i++) {
                    if (mJoysticks.get(i).device_id == device_id) {
                        mJoysticks.remove(i);
                        break;
                    }
                }
            }
        }
    }
    protected SDLJoystick getJoystick(int device_id) {
        for(int i=0; i < mJoysticks.size(); i++) {
            if (mJoysticks.get(i).device_id == device_id) {
                return mJoysticks.get(i);
        for (SDLJoystick joystick : mJoysticks) {
            if (joystick.device_id == device_id) {
                return joystick;
            }
        }
        return null;
@@ -232,39 +258,26 @@
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
            int actionPointerIndex = event.getActionIndex();
            int action = event.getActionMasked();
            switch(action) {
                case MotionEvent.ACTION_MOVE:
                    SDLJoystick joystick = getJoystick(event.getDeviceId());
                    if ( joystick != null ) {
                        for (int i = 0; i < joystick.axes.size(); i++) {
                            InputDevice.MotionRange range = joystick.axes.get(i);
                            /* Normalize the value to -1...1 */
                            float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
                            SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
                        }
                        for (int i = 0; i < joystick.hats.size(); i+=2) {
                            int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
                            int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
                            SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
                        }
            if (action == MotionEvent.ACTION_MOVE) {
                SDLJoystick joystick = getJoystick(event.getDeviceId());
                if (joystick != null) {
                    for (int i = 0; i < joystick.axes.size(); i++) {
                        InputDevice.MotionRange range = joystick.axes.get(i);
                        /* Normalize the value to -1...1 */
                        float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
                        SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
                    }
                    break;
                default:
                    break;
                    for (int i = 0; i < joystick.hats.size() / 2; i++) {
                        int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
                        int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
                        SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
                    }
                }
            }
        }
        return true;
    }
    public String getJoystickDescriptor(InputDevice joystickDevice) {
        return joystickDevice.getName();
    }
}
class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 {
    @Override
    public String getJoystickDescriptor(InputDevice joystickDevice) {
        String desc = joystickDevice.getDescriptor();
@@ -272,33 +285,187 @@
            return desc;
        }
        return super.getJoystickDescriptor(joystickDevice);
        return joystickDevice.getName();
    }
    public int getProductId(InputDevice joystickDevice) {
        return 0;
    }
    public int getVendorId(InputDevice joystickDevice) {
        return 0;
    }
    public int getButtonMask(InputDevice joystickDevice) {
        return -1;
    }
}
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
    @Override
    public int getProductId(InputDevice joystickDevice) {
        return joystickDevice.getProductId();
    }
    @Override
    public int getVendorId(InputDevice joystickDevice) {
        return joystickDevice.getVendorId();
    }
    @Override
    public int getButtonMask(InputDevice joystickDevice) {
        int button_mask = 0;
        int[] keys = new int[] {
            KeyEvent.KEYCODE_BUTTON_A,
            KeyEvent.KEYCODE_BUTTON_B,
            KeyEvent.KEYCODE_BUTTON_X,
            KeyEvent.KEYCODE_BUTTON_Y,
            KeyEvent.KEYCODE_BACK,
            KeyEvent.KEYCODE_BUTTON_MODE,
            KeyEvent.KEYCODE_BUTTON_START,
            KeyEvent.KEYCODE_BUTTON_THUMBL,
            KeyEvent.KEYCODE_BUTTON_THUMBR,
            KeyEvent.KEYCODE_BUTTON_L1,
            KeyEvent.KEYCODE_BUTTON_R1,
            KeyEvent.KEYCODE_DPAD_UP,
            KeyEvent.KEYCODE_DPAD_DOWN,
            KeyEvent.KEYCODE_DPAD_LEFT,
            KeyEvent.KEYCODE_DPAD_RIGHT,
            KeyEvent.KEYCODE_BUTTON_SELECT,
            KeyEvent.KEYCODE_DPAD_CENTER,
            // These don't map into any SDL controller buttons directly
            KeyEvent.KEYCODE_BUTTON_L2,
            KeyEvent.KEYCODE_BUTTON_R2,
            KeyEvent.KEYCODE_BUTTON_C,
            KeyEvent.KEYCODE_BUTTON_Z,
            KeyEvent.KEYCODE_BUTTON_1,
            KeyEvent.KEYCODE_BUTTON_2,
            KeyEvent.KEYCODE_BUTTON_3,
            KeyEvent.KEYCODE_BUTTON_4,
            KeyEvent.KEYCODE_BUTTON_5,
            KeyEvent.KEYCODE_BUTTON_6,
            KeyEvent.KEYCODE_BUTTON_7,
            KeyEvent.KEYCODE_BUTTON_8,
            KeyEvent.KEYCODE_BUTTON_9,
            KeyEvent.KEYCODE_BUTTON_10,
            KeyEvent.KEYCODE_BUTTON_11,
            KeyEvent.KEYCODE_BUTTON_12,
            KeyEvent.KEYCODE_BUTTON_13,
            KeyEvent.KEYCODE_BUTTON_14,
            KeyEvent.KEYCODE_BUTTON_15,
            KeyEvent.KEYCODE_BUTTON_16,
        };
        int[] masks = new int[] {
            (1 << 0),   // A -> A
            (1 << 1),   // B -> B
            (1 << 2),   // X -> X
            (1 << 3),   // Y -> Y
            (1 << 4),   // BACK -> BACK
            (1 << 5),   // MODE -> GUIDE
            (1 << 6),   // START -> START
            (1 << 7),   // THUMBL -> LEFTSTICK
            (1 << 8),   // THUMBR -> RIGHTSTICK
            (1 << 9),   // L1 -> LEFTSHOULDER
            (1 << 10),  // R1 -> RIGHTSHOULDER
            (1 << 11),  // DPAD_UP -> DPAD_UP
            (1 << 12),  // DPAD_DOWN -> DPAD_DOWN
            (1 << 13),  // DPAD_LEFT -> DPAD_LEFT
            (1 << 14),  // DPAD_RIGHT -> DPAD_RIGHT
            (1 << 4),   // SELECT -> BACK
            (1 << 0),   // DPAD_CENTER -> A
            (1 << 15),  // L2 -> ??
            (1 << 16),  // R2 -> ??
            (1 << 17),  // C -> ??
            (1 << 18),  // Z -> ??
            (1 << 20),  // 1 -> ??
            (1 << 21),  // 2 -> ??
            (1 << 22),  // 3 -> ??
            (1 << 23),  // 4 -> ??
            (1 << 24),  // 5 -> ??
            (1 << 25),  // 6 -> ??
            (1 << 26),  // 7 -> ??
            (1 << 27),  // 8 -> ??
            (1 << 28),  // 9 -> ??
            (1 << 29),  // 10 -> ??
            (1 << 30),  // 11 -> ??
            (1 << 31),  // 12 -> ??
            // We're out of room...
            0xFFFFFFFF,  // 13 -> ??
            0xFFFFFFFF,  // 14 -> ??
            0xFFFFFFFF,  // 15 -> ??
            0xFFFFFFFF,  // 16 -> ??
        };
        boolean[] has_keys = joystickDevice.hasKeys(keys);
        for (int i = 0; i < keys.length; ++i) {
            if (has_keys[i]) {
                button_mask |= masks[i];
            }
        }
        return button_mask;
    }
}
class SDLHapticHandler_API26 extends SDLHapticHandler {
    @Override
    public void run(int device_id, float intensity, int length) {
        SDLHaptic haptic = getHaptic(device_id);
        if (haptic != null) {
            Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
            if (intensity == 0.0f) {
                stop(device_id);
                return;
            }
            int vibeValue = Math.round(intensity * 255);
            if (vibeValue > 255) {
                vibeValue = 255;
            }
            if (vibeValue < 1) {
                stop(device_id);
                return;
            }
            try {
                haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
            }
            catch (Exception e) {
                // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
                // something went horribly wrong with the Android 8.0 APIs.
                haptic.vib.vibrate(length);
            }
        }
    }
}
class SDLHapticHandler {
    class SDLHaptic {
    static class SDLHaptic {
        public int device_id;
        public String name;
        public Vibrator vib;
    }
    private ArrayList<SDLHaptic> mHaptics;
    private final ArrayList<SDLHaptic> mHaptics;
    public SDLHapticHandler() {
        mHaptics = new ArrayList<SDLHaptic>();
    }
    public void run(int device_id, int length) {
    public void run(int device_id, float intensity, int length) {
        SDLHaptic haptic = getHaptic(device_id);
        if (haptic != null) {
            haptic.vib.vibrate (length);
            haptic.vib.vibrate(length);
        }
    }
    public void stop(int device_id) {
        SDLHaptic haptic = getHaptic(device_id);
        if (haptic != null) {
            haptic.vib.cancel();
        }
    }
    public void pollHapticDevices() {
        final int deviceId_VIBRATOR_SERVICE = 999999;
        boolean hasVibratorService = false;
@@ -308,21 +475,18 @@
        // so the first controller seen by SDL matches what the receiver
        // considers to be the first controller
        if (Build.VERSION.SDK_INT >= 16)
        {
            for (int i = deviceIds.length - 1; i > -1; i--) {
                SDLHaptic haptic = getHaptic(deviceIds[i]);
                if (haptic == null) {
                    InputDevice device = InputDevice.getDevice(deviceIds[i]);
                    Vibrator vib = device.getVibrator();
                    if (vib.hasVibrator()) {
                        haptic = new SDLHaptic();
                        haptic.device_id = deviceIds[i];
                        haptic.name = device.getName();
                        haptic.vib = vib;
                        mHaptics.add(haptic);
                        SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
                    }
        for (int i = deviceIds.length - 1; i > -1; i--) {
            SDLHaptic haptic = getHaptic(deviceIds[i]);
            if (haptic == null) {
                InputDevice device = InputDevice.getDevice(deviceIds[i]);
                Vibrator vib = device.getVibrator();
                if (vib.hasVibrator()) {
                    haptic = new SDLHaptic();
                    haptic.device_id = deviceIds[i];
                    haptic.name = device.getName();
                    haptic.vib = vib;
                    mHaptics.add(haptic);
                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
                }
            }
        }
@@ -330,11 +494,7 @@
        /* Check VIBRATOR_SERVICE */
        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
        if (vib != null) {
            if (Build.VERSION.SDK_INT >= 11) {
                hasVibratorService = vib.hasVibrator();
            } else {
                hasVibratorService = true;
            }
            hasVibratorService = vib.hasVibrator();
            if (hasVibratorService) {
                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
@@ -342,7 +502,7 @@
                    haptic = new SDLHaptic();
                    haptic.device_id = deviceId_VIBRATOR_SERVICE;
                    haptic.name = "VIBRATOR_SERVICE";
                    haptic.vib = vib;
                    haptic.vib = vib;
                    mHaptics.add(haptic);
                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
                }
@@ -350,41 +510,45 @@
        }
        /* Check removed devices */
        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
        for(int i=0; i < mHaptics.size(); i++) {
            int device_id = mHaptics.get(i).device_id;
            int j;
            for (j=0; j < deviceIds.length; j++) {
                if (device_id == deviceIds[j]) break;
        ArrayList<Integer> removedDevices = null;
        for (SDLHaptic haptic : mHaptics) {
            int device_id = haptic.device_id;
            int i;
            for (i = 0; i < deviceIds.length; i++) {
                if (device_id == deviceIds[i]) break;
            }
            if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) {
                // don't remove the vibrator if it is still present
            } else if (j == deviceIds.length) {
                removedDevices.add(device_id);
            }
            if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
                if (i == deviceIds.length) {
                    if (removedDevices == null) {
                        removedDevices = new ArrayList<Integer>();
                    }
                    removedDevices.add(device_id);
                }
            }  // else: don't remove the vibrator if it is still present
        }
        for(int i=0; i < removedDevices.size(); i++) {
            int device_id = removedDevices.get(i);
            SDLControllerManager.nativeRemoveHaptic(device_id);
            for (int j=0; j < mHaptics.size(); j++) {
                if (mHaptics.get(j).device_id == device_id) {
                    mHaptics.remove(j);
                    break;
        if (removedDevices != null) {
            for (int device_id : removedDevices) {
                SDLControllerManager.nativeRemoveHaptic(device_id);
                for (int i = 0; i < mHaptics.size(); i++) {
                    if (mHaptics.get(i).device_id == device_id) {
                        mHaptics.remove(i);
                        break;
                    }
                }
            }
        }
    }
    protected SDLHaptic getHaptic(int device_id) {
        for(int i=0; i < mHaptics.size(); i++) {
            if (mHaptics.get(i).device_id == device_id) {
                return mHaptics.get(i);
        for (SDLHaptic haptic : mHaptics) {
            if (haptic.device_id == device_id) {
                return haptic;
            }
        }
        return null;
    }
    }
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
@@ -401,22 +565,19 @@
                return SDLControllerManager.handleJoystickMotionEvent(event);
            case InputDevice.SOURCE_MOUSE:
                if (!SDLActivity.mSeparateMouseAndTouch) {
                    break;
                }
                action = event.getActionMasked();
                switch (action) {
                    case MotionEvent.ACTION_SCROLL:
                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
                        SDLActivity.onNativeMouse(0, action, x, y);
                        SDLActivity.onNativeMouse(0, action, x, y, false);
                        return true;
                    case MotionEvent.ACTION_HOVER_MOVE:
                        x = event.getX(0);
                        y = event.getY(0);
                        SDLActivity.onNativeMouse(0, action, x, y);
                        SDLActivity.onNativeMouse(0, action, x, y, false);
                        return true;
                    default:
@@ -431,5 +592,201 @@
        // Event was not managed
        return false;
    }
    public boolean supportsRelativeMouse() {
        return false;
    }
    public boolean inRelativeMode() {
        return false;
    }
    public boolean setRelativeMouseEnabled(boolean enabled) {
        return false;
    }
    public void reclaimRelativeMouseModeIfNeeded()
    {
    }
    public float getEventX(MotionEvent event) {
        return event.getX(0);
    }
    public float getEventY(MotionEvent event) {
        return event.getY(0);
    }
}
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
    // Generic Motion (mouse hover, joystick...) events go here
    private boolean mRelativeModeEnabled;
    @Override
    public boolean onGenericMotion(View v, MotionEvent event) {
        // Handle relative mouse mode
        if (mRelativeModeEnabled) {
            if (event.getSource() == InputDevice.SOURCE_MOUSE) {
                int action = event.getActionMasked();
                if (action == MotionEvent.ACTION_HOVER_MOVE) {
                    float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
                    float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
                    SDLActivity.onNativeMouse(0, action, x, y, true);
                    return true;
                }
            }
        }
        // Event was not managed, call SDLGenericMotionListener_API12 method
        return super.onGenericMotion(v, event);
    }
    @Override
    public boolean supportsRelativeMouse() {
        return true;
    }
    @Override
    public boolean inRelativeMode() {
        return mRelativeModeEnabled;
    }
    @Override
    public boolean setRelativeMouseEnabled(boolean enabled) {
        mRelativeModeEnabled = enabled;
        return true;
    }
    @Override
    public float getEventX(MotionEvent event) {
        if (mRelativeModeEnabled) {
            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
        } else {
            return event.getX(0);
        }
    }
    @Override
    public float getEventY(MotionEvent event) {
        if (mRelativeModeEnabled) {
            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
        } else {
            return event.getY(0);
        }
    }
}
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
    // Generic Motion (mouse hover, joystick...) events go here
    private boolean mRelativeModeEnabled;
    @Override
    public boolean onGenericMotion(View v, MotionEvent event) {
        float x, y;
        int action;
        switch ( event.getSource() ) {
            case InputDevice.SOURCE_JOYSTICK:
            case InputDevice.SOURCE_GAMEPAD:
            case InputDevice.SOURCE_DPAD:
                return SDLControllerManager.handleJoystickMotionEvent(event);
            case InputDevice.SOURCE_MOUSE:
            // DeX desktop mouse cursor is a separate non-standard input type.
            case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
                action = event.getActionMasked();
                switch (action) {
                    case MotionEvent.ACTION_SCROLL:
                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
                        SDLActivity.onNativeMouse(0, action, x, y, false);
                        return true;
                    case MotionEvent.ACTION_HOVER_MOVE:
                        x = event.getX(0);
                        y = event.getY(0);
                        SDLActivity.onNativeMouse(0, action, x, y, false);
                        return true;
                    default:
                        break;
                }
                break;
            case InputDevice.SOURCE_MOUSE_RELATIVE:
                action = event.getActionMasked();
                switch (action) {
                    case MotionEvent.ACTION_SCROLL:
                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
                        SDLActivity.onNativeMouse(0, action, x, y, false);
                        return true;
                    case MotionEvent.ACTION_HOVER_MOVE:
                        x = event.getX(0);
                        y = event.getY(0);
                        SDLActivity.onNativeMouse(0, action, x, y, true);
                        return true;
                    default:
                        break;
                }
                break;
            default:
                break;
        }
        // Event was not managed
        return false;
    }
    @Override
    public boolean supportsRelativeMouse() {
        return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
    }
    @Override
    public boolean inRelativeMode() {
        return mRelativeModeEnabled;
    }
    @Override
    public boolean setRelativeMouseEnabled(boolean enabled) {
        if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
            if (enabled) {
                SDLActivity.getContentView().requestPointerCapture();
            } else {
                SDLActivity.getContentView().releasePointerCapture();
            }
            mRelativeModeEnabled = enabled;
            return true;
        } else {
            return false;
        }
    }
    @Override
    public void reclaimRelativeMouseModeIfNeeded()
    {
        if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
            SDLActivity.getContentView().requestPointerCapture();
        }
    }
    @Override
    public float getEventX(MotionEvent event) {
        // Relative mouse in capture mode will only have relative for X/Y
        return event.getX(0);
    }
    @Override
    public float getEventY(MotionEvent event) {
        // Relative mouse in capture mode will only have relative for X/Y
        return event.getY(0);
    }
}