Skip to content

Commit

Permalink
Feat[gamepad]: direct input
Browse files Browse the repository at this point in the history
  • Loading branch information
artdeell committed Dec 27, 2024
1 parent 99c8ea2 commit c5d1739
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 28 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1732218529630
1735293224932
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.gamepad.DefaultDataProvider;
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
import net.kdt.pojavlaunch.customcontrols.gamepad.direct.DirectGamepad;
import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad;
import net.kdt.pojavlaunch.customcontrols.mouse.AndroidPointerCapture;
import net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor;
Expand All @@ -40,6 +41,7 @@

import org.lwjgl.glfw.CallbackBridge;

import fr.spse.gamepad_remapper.GamepadHandler;
import fr.spse.gamepad_remapper.RemapperManager;
import fr.spse.gamepad_remapper.RemapperView;

Expand All @@ -48,7 +50,7 @@
*/
public class MinecraftGLSurface extends View implements GrabListener {
/* Gamepad object for gamepad inputs, instantiated on need */
private Gamepad mGamepad = null;
private GamepadHandler mGamepadHandler;
/* The RemapperView.Builder object allows you to set which buttons to remap */
private final RemapperManager mInputManager = new RemapperManager(getContext(), new RemapperView.Builder(null)
.remapA(true)
Expand Down Expand Up @@ -203,7 +205,11 @@ public boolean onTouchEvent(MotionEvent e) {
}

private void createGamepad(View contextView, InputDevice inputDevice) {
mGamepad = new Gamepad(contextView, inputDevice, DefaultDataProvider.INSTANCE, true);
if(LauncherPreferences.PREF_DIRECT_CONTROLLER) {
mGamepadHandler = new DirectGamepad();
}else {
mGamepadHandler = new Gamepad(contextView, inputDevice, DefaultDataProvider.INSTANCE, true);
}
}

/**
Expand All @@ -215,9 +221,9 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) {
int mouseCursorIndex = -1;

if(Gamepad.isGamepadEvent(event)){
if(mGamepad == null) createGamepad(this, event.getDevice());
if(mGamepadHandler == null) createGamepad(this, event.getDevice());

mInputManager.handleMotionEventInput(getContext(), event, mGamepad);
mInputManager.handleMotionEventInput(getContext(), event, mGamepadHandler);
return true;
}

Expand Down Expand Up @@ -287,9 +293,9 @@ public boolean processKeyEvent(KeyEvent event) {
}

if(Gamepad.isGamepadEvent(event)){
if(mGamepad == null) createGamepad(this, event.getDevice());
if(mGamepadHandler == null) createGamepad(this, event.getDevice());

mInputManager.handleKeyEventInput(getContext(), event, mGamepad);
mInputManager.handleKeyEventInput(getContext(), event, mGamepadHandler);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import net.kdt.pojavlaunch.GrabListener;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.MCOptionUtils;

import org.lwjgl.glfw.CallbackBridge;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.kdt.pojavlaunch.customcontrols.gamepad.direct;

import static android.view.MotionEvent.AXIS_HAT_X;
import static android.view.MotionEvent.AXIS_HAT_Y;
import static org.lwjgl.glfw.CallbackBridge.sGamepadAxisBuffer;
import static org.lwjgl.glfw.CallbackBridge.sGamepadButtonBuffer;

import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

import fr.spse.gamepad_remapper.GamepadHandler;

public class DirectGamepad implements GamepadHandler {
@Override
public void handleGamepadInput(int keycode, float value) {
int gKeycode = -1, gAxis = -1;
switch (keycode) {
case KeyEvent.KEYCODE_BUTTON_A: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_A; break;
case KeyEvent.KEYCODE_BUTTON_B: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_B; break;
case KeyEvent.KEYCODE_BUTTON_X: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_X; break;
case KeyEvent.KEYCODE_BUTTON_Y: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_Y; break;
case KeyEvent.KEYCODE_BUTTON_L1: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER; break;
case KeyEvent.KEYCODE_BUTTON_R1: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER; break;
case KeyEvent.KEYCODE_BUTTON_L2:
case MotionEvent.AXIS_LTRIGGER:
gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
case MotionEvent.AXIS_RTRIGGER:
gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_LEFT_THUMB; break;
case KeyEvent.KEYCODE_BUTTON_THUMBR: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB; break;
case KeyEvent.KEYCODE_BUTTON_START: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_START; break;
case KeyEvent.KEYCODE_BUTTON_SELECT: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_BACK; break;
case KeyEvent.KEYCODE_DPAD_UP: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_UP; break;
case KeyEvent.KEYCODE_DPAD_DOWN: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_DOWN; break;
case KeyEvent.KEYCODE_DPAD_LEFT: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_LEFT; break;
case KeyEvent.KEYCODE_DPAD_RIGHT: gKeycode = GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT; break;
case KeyEvent.KEYCODE_DPAD_CENTER: break; // TODO
case MotionEvent.AXIS_X: gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_LEFT_X; break;
case MotionEvent.AXIS_Y: gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_LEFT_Y; break;
case MotionEvent.AXIS_Z: gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_RIGHT_X; break;
case MotionEvent.AXIS_RZ: gAxis = GamepadKeycodes.GLFW_GAMEPAD_AXIS_RIGHT_Y; break;
case AXIS_HAT_X:
sGamepadButtonBuffer.put(
GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_LEFT,
value < -0.85 ? GamepadKeycodes.GLFW_PRESS : GamepadKeycodes.GLFW_RELEASE
);
sGamepadButtonBuffer.put(
GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,
value > 0.85 ? GamepadKeycodes.GLFW_PRESS : GamepadKeycodes.GLFW_RELEASE
);
return;
case AXIS_HAT_Y:
sGamepadButtonBuffer.put(
GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_UP,
value < -0.85 ? GamepadKeycodes.GLFW_PRESS : GamepadKeycodes.GLFW_RELEASE
);
sGamepadButtonBuffer.put(
GamepadKeycodes.GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
value > 0.85 ? GamepadKeycodes.GLFW_PRESS : GamepadKeycodes.GLFW_RELEASE
);
return;
}
if(gKeycode != -1) {
sGamepadButtonBuffer.put(gKeycode, value > 0.85 ? GamepadKeycodes.GLFW_PRESS : GamepadKeycodes.GLFW_RELEASE);
}
if(gAxis != -1) {
sGamepadAxisBuffer.put(gAxis, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.kdt.pojavlaunch.customcontrols.gamepad.direct;

public class GamepadKeycodes {
public static final byte GLFW_RELEASE = 0;
public static final byte GLFW_PRESS = 1;
public static int NUM_KEYCODES = 0;

public static final short GLFW_GAMEPAD_BUTTON_A = 0;
public static final short GLFW_GAMEPAD_BUTTON_B = 1;
public static final short GLFW_GAMEPAD_BUTTON_X = 2;
public static final short GLFW_GAMEPAD_BUTTON_Y = 3;
public static final short GLFW_GAMEPAD_BUTTON_LEFT_BUMPER = 4;
public static final short GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER = 5;
public static final short GLFW_GAMEPAD_BUTTON_BACK = 6;
public static final short GLFW_GAMEPAD_BUTTON_START = 7;
public static final short GLFW_GAMEPAD_BUTTON_GUIDE = 8;
public static final short GLFW_GAMEPAD_BUTTON_LEFT_THUMB = 9;
public static final short GLFW_GAMEPAD_BUTTON_RIGHT_THUMB = 10;
public static final short GLFW_GAMEPAD_BUTTON_DPAD_UP = 11;
public static final short GLFW_GAMEPAD_BUTTON_DPAD_RIGHT = 12;
public static final short GLFW_GAMEPAD_BUTTON_DPAD_DOWN = 13;
public static final short GLFW_GAMEPAD_BUTTON_DPAD_LEFT = 14;
public static final short GLFW_GAMEPAD_BUTTON_LAST = GLFW_GAMEPAD_BUTTON_DPAD_LEFT;
public static final short GLFW_GAMEPAD_BUTTON_CROSS = GLFW_GAMEPAD_BUTTON_A;
public static final short GLFW_GAMEPAD_BUTTON_CIRCLE = GLFW_GAMEPAD_BUTTON_B;
public static final short GLFW_GAMEPAD_BUTTON_SQUARE = GLFW_GAMEPAD_BUTTON_X;
public static final short GLFW_GAMEPAD_BUTTON_TRIANGLE = GLFW_GAMEPAD_BUTTON_Y;

public static final short GLFW_GAMEPAD_AXIS_LEFT_X = 0;
public static final short GLFW_GAMEPAD_AXIS_LEFT_Y = 1;
public static final short GLFW_GAMEPAD_AXIS_RIGHT_X = 2;
public static final short GLFW_GAMEPAD_AXIS_RIGHT_Y = 3;
public static final short GLFW_GAMEPAD_AXIS_LEFT_TRIGGER = 4;
public static final short GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER = 5;
public static final short GLFW_GAMEPAD_AXIS_LAST = GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class LauncherPreferences {
public static String PREF_DOWNLOAD_SOURCE = "default";
public static boolean PREF_SKIP_NOTIFICATION_PERMISSION_CHECK = false;
public static boolean PREF_VSYNC_IN_ZINK = true;
public static boolean PREF_DIRECT_CONTROLLER = false;


public static void loadPreferences(Context ctx) {
Expand Down Expand Up @@ -109,6 +110,7 @@ public static void loadPreferences(Context ctx) {
PREF_VERIFY_MANIFEST = DEFAULT_PREF.getBoolean("verifyManifest", true);
PREF_SKIP_NOTIFICATION_PERMISSION_CHECK = DEFAULT_PREF.getBoolean(PREF_KEY_SKIP_NOTIFICATION_CHECK, false);
PREF_VSYNC_IN_ZINK = DEFAULT_PREF.getBoolean("vsync_in_zink", true);
PREF_DIRECT_CONTROLLER = DEFAULT_PREF.getBoolean("directController", false);

String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ private void computeVisibility(){
requirePreference("gyroInvertX").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
requirePreference("gyroInvertY").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
requirePreference("gyroSmoothing").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
requirePreference("_frag_changeKeyBindings").setVisible(!LauncherPreferences.PREF_DIRECT_CONTROLLER);
}

}
15 changes: 15 additions & 0 deletions app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import androidx.annotation.Keep;
import androidx.annotation.Nullable;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;

import dalvik.annotation.optimization.CriticalNative;
Expand All @@ -27,6 +30,9 @@ public class CallbackBridge {
public volatile static boolean holdingAlt, holdingCapslock, holdingCtrl,
holdingNumlock, holdingShift;

public static final ByteBuffer sGamepadButtonBuffer;
public static final FloatBuffer sGamepadAxisBuffer;

public static void putMouseEventWithCoords(int button, float x, float y) {
putMouseEventWithCoords(button, true, x, y);
sChoreographer.postFrameCallbackDelayed(l -> putMouseEventWithCoords(button, false, x, y), 33);
Expand Down Expand Up @@ -193,6 +199,11 @@ public static void removeGrabListener(GrabListener listener) {
grabListeners.remove(listener);
}
}
public static FloatBuffer createGamepadAxisBuffer() {
ByteBuffer axisByteBuffer = nativeCreateGamepadAxisBuffer();
// NOTE: hardcoded order (also in jre_lwjgl3glfw CallbackBridge)
return axisByteBuffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
}

@Keep @CriticalNative public static native void nativeSetUseInputStackQueue(boolean useInputStackQueue);

Expand All @@ -206,8 +217,12 @@ public static void removeGrabListener(GrabListener listener) {
@Keep @CriticalNative private static native void nativeSendScroll(double xoffset, double yoffset);
@Keep @CriticalNative private static native void nativeSendScreenSize(int width, int height);
public static native void nativeSetWindowAttrib(int attrib, int value);
private static native ByteBuffer nativeCreateGamepadButtonBuffer();
private static native ByteBuffer nativeCreateGamepadAxisBuffer();
static {
System.loadLibrary("pojavexec");
sGamepadButtonBuffer = nativeCreateGamepadButtonBuffer();
sGamepadAxisBuffer = createGamepadAxisBuffer();
}
}

6 changes: 6 additions & 0 deletions app_pojavlauncher/src/main/jni/environ/environ.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ typedef void GLFW_invoke_Key_func(void* window, int key, int scancode, int actio
typedef void GLFW_invoke_MouseButton_func(void* window, int button, int action, int mods);
typedef void GLFW_invoke_Scroll_func(void* window, double xoffset, double yoffset);

typedef struct {
unsigned char buttons [15];
float axes[6];
} GLFWgamepadstate;

struct pojav_environ_s {
struct ANativeWindow* pojavWindow;
basic_render_window_t* mainWindowBundle;
Expand Down Expand Up @@ -58,6 +63,7 @@ struct pojav_environ_s {
bool isInputReady, isCursorEntered, isUseStackQueueCall, shouldUpdateMouse;
bool shouldUpdateMonitorSize, monitorSizeConsumed;
int savedWidth, savedHeight;
GLFWgamepadstate gamepadState;
#define ADD_CALLBACK_WWIN(NAME) \
GLFW_invoke_##NAME##_func* GLFW_invoke_##NAME;
ADD_CALLBACK_WWIN(Char);
Expand Down
22 changes: 19 additions & 3 deletions app_pojavlauncher/src/main/jni/input_bridge_v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) {
//Save dalvik global JavaVM pointer
pojav_environ->dalvikJavaVMPtr = vm;
(*vm)->GetEnv(vm, (void**) &pojav_environ->dalvikJNIEnvPtr_ANDROID, JNI_VERSION_1_4);
pojav_environ->bridgeClazz = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->NewGlobalRef(pojav_environ->dalvikJNIEnvPtr_ANDROID,(*pojav_environ->dalvikJNIEnvPtr_ANDROID) ->FindClass(pojav_environ->dalvikJNIEnvPtr_ANDROID,"org/lwjgl/glfw/CallbackBridge"));
pojav_environ->method_accessAndroidClipboard = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(pojav_environ->dalvikJNIEnvPtr_ANDROID, pojav_environ->bridgeClazz, "accessAndroidClipboard", "(ILjava/lang/String;)Ljava/lang/String;");
pojav_environ->method_onGrabStateChanged = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(pojav_environ->dalvikJNIEnvPtr_ANDROID, pojav_environ->bridgeClazz, "onGrabStateChanged", "(Z)V");
JNIEnv *env = pojav_environ->dalvikJNIEnvPtr_ANDROID;
pojav_environ->bridgeClazz = (*env)->NewGlobalRef(env,(*env) ->FindClass(env,"org/lwjgl/glfw/CallbackBridge"));
pojav_environ->method_accessAndroidClipboard = (*env)->GetStaticMethodID(env, pojav_environ->bridgeClazz, "accessAndroidClipboard", "(ILjava/lang/String;)Ljava/lang/String;");
pojav_environ->method_onGrabStateChanged = (*env)->GetStaticMethodID(env, pojav_environ->bridgeClazz, "onGrabStateChanged", "(Z)V");
pojav_environ->isUseStackQueueCall = JNI_FALSE;
} else if (pojav_environ->dalvikJavaVMPtr != vm) {
__android_log_print(ANDROID_LOG_INFO, "Native", "Saving JVM environ...");
Expand Down Expand Up @@ -644,3 +645,18 @@ static void registerFunctions(JNIEnv *env) {
use_critical_cc ? critical_fcns : noncritical_fcns,
sizeof(critical_fcns)/sizeof(critical_fcns[0]));
}

JNIEXPORT jlong JNICALL
Java_org_lwjgl_glfw_GLFW_internalGetGamepadDataPointer(JNIEnv *env, jclass clazz) {
return (jlong) &pojav_environ->gamepadState;
}

JNIEXPORT jobject JNICALL
Java_org_lwjgl_glfw_CallbackBridge_nativeCreateGamepadButtonBuffer(JNIEnv *env, jclass clazz) {
return (*env)->NewDirectByteBuffer(env, &pojav_environ->gamepadState.buttons, sizeof(pojav_environ->gamepadState.buttons));
}

JNIEXPORT jobject JNICALL
Java_org_lwjgl_glfw_CallbackBridge_nativeCreateGamepadAxisBuffer(JNIEnv *env, jclass clazz) {
return (*env)->NewDirectByteBuffer(env, &pojav_environ->gamepadState.axes, sizeof(pojav_environ->gamepadState.axes));
}
2 changes: 2 additions & 0 deletions app_pojavlauncher/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,6 @@
<string name="local_login_bad_username_title">Unsuitable username</string>
<string name="local_login_bad_username_text">The username must be between 3–16 characters long, and must only contain latin letters, arabic numerals and underscores.</string>
<string name="quick_setting_title">Quick settings</string>
<string name="preference_direct_controller_title">Use direct controller input</string>
<string name="preference_direct_controller_description">Disables keyboard/mouse emulation and lets the game use gamepad inputs directly.</string>
</resources>
6 changes: 6 additions & 0 deletions app_pojavlauncher/src/main/res/xml/pref_control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@
<PreferenceCategory
android:title="@string/preference_category_controller_settings"
>
<SwitchPreference
android:key="directController"
android:title="@string/preference_direct_controller_title"
android:summary="@string/preference_direct_controller_description"
/>
<Preference
android:key="_frag_changeKeyBindings"
android:title="@string/preference_remap_controller_title"
android:summary="@string/preference_remap_controller_description"
android:fragment="net.kdt.pojavlaunch.fragments.GamepadMapperFragment"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package org.lwjgl.glfw;
import java.nio.ByteBuffer;
import java.util.*;

public class CallbackBridge {
Expand Down Expand Up @@ -47,5 +48,7 @@ public static void sendData(int type, String data) {
public static native boolean nativeSetInputReady(boolean ready);
public static native String nativeClipboard(int action, byte[] copy);
public static native void nativeSetGrabbing(boolean grab);
public static native ByteBuffer nativeCreateGamepadButtonBuffer();
public static native ByteBuffer nativeCreateGamepadAxisBuffer();
}

Loading

0 comments on commit c5d1739

Please sign in to comment.