Feat[gamepad_direct]: detect usage of GLFW gamepad automatically

This commit is contained in:
artdeell 2024-12-27 22:54:00 +03:00
parent c5d1739877
commit f0005fa912
16 changed files with 109 additions and 33 deletions

View File

@ -1 +1 @@
1735293224932
1735328970475

View File

@ -30,6 +30,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.gamepad.direct.DirectGamepadEnableHandler;
import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad;
import net.kdt.pojavlaunch.customcontrols.mouse.AndroidPointerCapture;
import net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor;
@ -48,7 +49,7 @@ import fr.spse.gamepad_remapper.RemapperView;
/**
* Class dealing with showing minecraft surface and taking inputs to dispatch them to minecraft
*/
public class MinecraftGLSurface extends View implements GrabListener {
public class MinecraftGLSurface extends View implements GrabListener, DirectGamepadEnableHandler {
/* Gamepad object for gamepad inputs, instantiated on need */
private GamepadHandler mGamepadHandler;
/* The RemapperView.Builder object allows you to set which buttons to remap */
@ -90,6 +91,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
public MinecraftGLSurface(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setFocusable(true);
CallbackBridge.setDirectGamepadEnableHandler(this);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@ -205,7 +207,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
}
private void createGamepad(View contextView, InputDevice inputDevice) {
if(LauncherPreferences.PREF_DIRECT_CONTROLLER) {
if(CallbackBridge.sGamepadDirectInput) {
mGamepadHandler = new DirectGamepad();
}else {
mGamepadHandler = new Gamepad(contextView, inputDevice, DefaultDataProvider.INSTANCE, true);
@ -411,6 +413,17 @@ public class MinecraftGLSurface extends View implements GrabListener {
}
}
@Override
public void onDirectGamepadEnabled() {
post(()->{
if(mGamepadHandler != null && mGamepadHandler instanceof Gamepad) {
((Gamepad)mGamepadHandler).removeSelf();
}
// Force gamepad recreation on next event
mGamepadHandler = null;
});
}
/** A small interface called when the listener is ready for the first time */
public interface SurfaceReadyListener {
void isReady();

View File

@ -30,4 +30,9 @@ public class DefaultDataProvider implements GamepadDataProvider {
public void attachGrabListener(GrabListener grabListener) {
CallbackBridge.addGrabListener(grabListener);
}
@Override
public void detachGrabListener(GrabListener grabListener) {
CallbackBridge.removeGrabListener(grabListener);
}
}

View File

@ -87,6 +87,8 @@ public class Gamepad implements GrabListener, GamepadHandler {
private final GamepadDataProvider mMapProvider;
private boolean mRemoved = false;
public Gamepad(View contextView, InputDevice inputDevice, GamepadDataProvider mapProvider, boolean showCursor){
Settings.setDeadzoneScale(PREF_DEADZONE_SCALE);
@ -95,7 +97,7 @@ public class Gamepad implements GrabListener, GamepadHandler {
@Override
public void doFrame(long frameTimeNanos) {
tick(frameTimeNanos);
mScreenChoreographer.postFrameCallback(this);
if(!mRemoved) mScreenChoreographer.postFrameCallback(this);
}
};
mScreenChoreographer.postFrameCallback(frameCallback);
@ -452,4 +454,10 @@ public class Gamepad implements GrabListener, GamepadHandler {
break;
}
}
public void removeSelf() {
mRemoved = true;
mMapProvider.detachGrabListener(this);
ViewGroup viewGroup = (ViewGroup) mPointerImageView.getParent();
if(viewGroup != null) viewGroup.removeView(mPointerImageView);
}
}

View File

@ -7,4 +7,5 @@ public interface GamepadDataProvider {
GamepadMap getGameMap();
boolean isGrabbing();
void attachGrabListener(GrabListener grabListener);
void detachGrabListener(GrabListener grabListener);
}

View File

@ -325,6 +325,11 @@ public class GamepadMapperAdapter extends RecyclerView.Adapter<GamepadMapperAdap
grabListener.onGrabState(mGrabState);
}
@Override
public void detachGrabListener(GrabListener grabListener) {
mGamepadGrabListener = null;
}
public void setGrabState(boolean newState) {
mGrabState = newState;
if(mGamepadGrabListener != null) mGamepadGrabListener.onGrabState(newState);

View File

@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.customcontrols.gamepad.direct;
public interface DirectGamepadEnableHandler {
void onDirectGamepadEnabled();
}

View File

@ -67,7 +67,6 @@ 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) {
@ -110,7 +109,6 @@ public class LauncherPreferences {
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)) {

View File

@ -2,8 +2,6 @@ package net.kdt.pojavlaunch.prefs.screens;
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import androidx.preference.PreferenceCategory;
@ -89,7 +87,6 @@ public class LauncherPreferenceControlFragment extends LauncherPreferenceFragmen
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);
}
}

View File

@ -1,6 +1,8 @@
package org.lwjgl.glfw;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.gamepad.direct.DirectGamepadEnableHandler;
import android.content.*;
import android.util.Log;
import android.view.Choreographer;
@ -8,6 +10,7 @@ import android.view.Choreographer;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@ -19,6 +22,7 @@ public class CallbackBridge {
public static final Choreographer sChoreographer = Choreographer.getInstance();
private static boolean isGrabbing = false;
private static final ArrayList<GrabListener> grabListeners = new ArrayList<>();
private static WeakReference<DirectGamepadEnableHandler> sDirectGamepadEnableHandler;
public static final int CLIPBOARD_COPY = 2000;
public static final int CLIPBOARD_PASTE = 2001;
@ -32,6 +36,7 @@ public class CallbackBridge {
public static final ByteBuffer sGamepadButtonBuffer;
public static final FloatBuffer sGamepadAxisBuffer;
public static boolean sGamepadDirectInput = false;
public static void putMouseEventWithCoords(int button, float x, float y) {
putMouseEventWithCoords(button, true, x, y);
@ -171,6 +176,16 @@ public class CallbackBridge {
}
}
//Called from JRE side
@SuppressWarnings("unused")
@Keep
private static void onDirectInputEnable() {
Log.i("CallbackBridge", "onDirectInputEnable()");
DirectGamepadEnableHandler enableHandler = Tools.getWeakReference(sDirectGamepadEnableHandler);
if(enableHandler != null) enableHandler.onDirectGamepadEnabled();
sGamepadDirectInput = true;
}
//Called from JRE side
@SuppressWarnings("unused")
@Keep
@ -199,12 +214,17 @@ public class CallbackBridge {
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();
}
public static void setDirectGamepadEnableHandler(DirectGamepadEnableHandler h) {
sDirectGamepadEnableHandler = new WeakReference<>(h);
}
@Keep @CriticalNative public static native void nativeSetUseInputStackQueue(boolean useInputStackQueue);
@Keep @CriticalNative private static native boolean nativeSendChar(char codepoint);

View File

@ -47,6 +47,7 @@ struct pojav_environ_s {
double cursorX, cursorY, cLastX, cLastY;
jmethodID method_accessAndroidClipboard;
jmethodID method_onGrabStateChanged;
jmethodID method_onDirectInputEnable;
jmethodID method_glftSetWindowAttrib;
jmethodID method_internalWindowSizeChanged;
jmethodID method_internalChangeMonitorSize;

View File

@ -30,6 +30,29 @@
#define EVENT_TYPE_MOUSE_BUTTON 1006
#define EVENT_TYPE_SCROLL 1007
static jint attach_env(JNIEnv ** pEnv, JavaVM *jvm) {
JNIEnv *jvm_env = NULL;
jint env_result = (*jvm)->GetEnv(jvm, (void**)&jvm_env, JNI_VERSION_1_4);
if(env_result == JNI_EDETACHED) {
env_result = (*jvm)->AttachCurrentThread(jvm, &jvm_env, NULL);
}
if(env_result != JNI_OK) {
return env_result;
}
*pEnv = jvm_env;
return JNI_OK;
}
#define TRY_ATTACH_ENV(env_name, vm, error_message, then) JNIEnv* env_name;\
do { \
jint result = attach_env(&env_name, vm); \
if(result != JNI_OK) { \
printf(error_message, result); \
then\
} \
} while(0)
jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream);
static void registerFunctions(JNIEnv *env);
@ -44,6 +67,7 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) {
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->method_onDirectInputEnable = (*env)->GetStaticMethodID(env, pojav_environ->bridgeClazz, "onDirectInputEnable", "()V");
pojav_environ->isUseStackQueueCall = JNI_FALSE;
} else if (pojav_environ->dalvikJavaVMPtr != vm) {
__android_log_print(ANDROID_LOG_INFO, "Native", "Saving JVM environ...");
@ -395,13 +419,18 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetInputRead
}
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetGrabbing(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jboolean grabbing) {
JNIEnv *dalvikEnv;
(*pojav_environ->dalvikJavaVMPtr)->AttachCurrentThread(pojav_environ->dalvikJavaVMPtr, &dalvikEnv, NULL);
(*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, pojav_environ->bridgeClazz, pojav_environ->method_onGrabStateChanged, grabbing);
(*pojav_environ->dalvikJavaVMPtr)->DetachCurrentThread(pojav_environ->dalvikJavaVMPtr);
TRY_ATTACH_ENV(dvm_env, pojav_environ->dalvikJavaVMPtr, "nativeSetGrabbing failed: %i", return;);
(*dvm_env)->CallStaticVoidMethod(dvm_env, pojav_environ->bridgeClazz, pojav_environ->method_onGrabStateChanged, grabbing);
pojav_environ->isGrabbing = grabbing;
}
JNIEXPORT jboolean JNICALL
Java_org_lwjgl_glfw_CallbackBridge_nativeEnableGamepadDirectInput(__attribute__((unused)) JNIEnv *env, __attribute__((unused)) jclass clazz) {
TRY_ATTACH_ENV(dvm_env, pojav_environ->dalvikJavaVMPtr, "nativeEnableGamepadDirectInput failed: %i", return JNI_FALSE;);
(*dvm_env)->CallStaticVoidMethod(dvm_env, pojav_environ->bridgeClazz, pojav_environ->method_onDirectInputEnable);
return JNI_TRUE;
}
jboolean critical_send_char(jchar codepoint) {
if (pojav_environ->GLFW_invoke_Char && pojav_environ->isInputReady) {
if (pojav_environ->isUseStackQueueCall) {
@ -561,16 +590,7 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(
// in environ for the Android UI thread but this is the only place that uses it
// (very rarely, only in lifecycle callbacks) so i dont care
JavaVM* jvm = pojav_environ->runtimeJavaVMPtr;
JNIEnv *jvm_env = NULL;
jint env_result = (*jvm)->GetEnv(jvm, (void**)&jvm_env, JNI_VERSION_1_4);
if(env_result == JNI_EDETACHED) {
env_result = (*jvm)->AttachCurrentThread(jvm, &jvm_env, NULL);
}
if(env_result != JNI_OK) {
printf("input_bridge nativeSetWindowAttrib() JNI call failed: %i\n", env_result);
return;
}
TRY_ATTACH_ENV(jvm_env, pojav_environ->runtimeJavaVMPtr, "nativeSetWindowAttrib failed: %i", return;);
(*jvm_env)->CallStaticVoidMethod(
jvm_env, pojav_environ->vmGlfwClass,

View File

@ -144,13 +144,7 @@
<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"/>

View File

@ -20,6 +20,8 @@ public class CallbackBridge {
public static final boolean INPUT_DEBUG_ENABLED;
public static boolean sGamepadDirectEnabled;
// TODO send grab state event to Android
static {
@ -41,6 +43,11 @@ public class CallbackBridge {
}
public static void enableGamepadDirectInput() {
if(sGamepadDirectEnabled) return;
sGamepadDirectEnabled = nativeEnableGamepadDirectInput();
}
public static void sendData(int type, String data) {
nativeSendData(false, type, data);
}
@ -50,5 +57,6 @@ public class CallbackBridge {
public static native void nativeSetGrabbing(boolean grab);
public static native ByteBuffer nativeCreateGamepadButtonBuffer();
public static native ByteBuffer nativeCreateGamepadAxisBuffer();
private static native boolean nativeEnableGamepadDirectInput();
}

View File

@ -1211,6 +1211,7 @@ public class GLFW
public static boolean glfwJoystickPresent(int jid) {
if(jid == GLFW_JOYSTICK_1) {
CallbackBridge.enableGamepadDirectInput();
return true;
}else return false;
}