Skip to content

Commit

Permalink
Feat[gamepad_direct]: detect usage of GLFW gamepad automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
artdeell committed Dec 27, 2024
1 parent c5d1739 commit f0005fa
Show file tree
Hide file tree
Showing 16 changed files with 109 additions and 33 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1735293224932
1735328970475
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand All @@ -48,7 +49,7 @@
/**
* 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 */
Expand Down Expand Up @@ -90,6 +91,7 @@ public MinecraftGLSurface(Context context) {
public MinecraftGLSurface(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setFocusable(true);
CallbackBridge.setDirectGamepadEnableHandler(this);
}

@RequiresApi(api = Build.VERSION_CODES.O)
Expand Down Expand Up @@ -205,7 +207,7 @@ public boolean onTouchEvent(MotionEvent e) {
}

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);
Expand Down Expand Up @@ -411,6 +413,17 @@ private void updateGrabState(boolean isGrabbing) {
}
}

@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ public boolean isGrabbing() {
public void attachGrabListener(GrabListener grabListener) {
CallbackBridge.addGrabListener(grabListener);
}

@Override
public void detachGrabListener(GrabListener grabListener) {
CallbackBridge.removeGrabListener(grabListener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -95,7 +97,7 @@ public Gamepad(View contextView, InputDevice inputDevice, GamepadDataProvider ma
@Override
public void doFrame(long frameTimeNanos) {
tick(frameTimeNanos);
mScreenChoreographer.postFrameCallback(this);
if(!mRemoved) mScreenChoreographer.postFrameCallback(this);
}
};
mScreenChoreographer.postFrameCallback(frameCallback);
Expand Down Expand Up @@ -452,4 +454,10 @@ public void handleGamepadInput(int keycode, float value) {
break;
}
}
public void removeSelf() {
mRemoved = true;
mMapProvider.detachGrabListener(this);
ViewGroup viewGroup = (ViewGroup) mPointerImageView.getParent();
if(viewGroup != null) viewGroup.removeView(mPointerImageView);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public interface GamepadDataProvider {
GamepadMap getGameMap();
boolean isGrabbing();
void attachGrabListener(GrabListener grabListener);
void detachGrabListener(GrabListener grabListener);
}
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,11 @@ public void attachGrabListener(GrabListener grabListener) {
grabListener.onGrabState(mGrabState);
}

@Override
public void detachGrabListener(GrabListener grabListener) {
mGamepadGrabListener = null;
}

public void setGrabState(boolean newState) {
mGrabState = newState;
if(mGamepadGrabListener != null) mGamepadGrabListener.onGrabState(newState);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.customcontrols.gamepad.direct;

public interface DirectGamepadEnableHandler {
void onDirectGamepadEnabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -110,7 +109,6 @@ 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 @@ -2,8 +2,6 @@

import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;

import androidx.preference.PreferenceCategory;
Expand Down Expand Up @@ -89,7 +87,6 @@ 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);
}

}
20 changes: 20 additions & 0 deletions app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;

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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -171,6 +176,16 @@ public static void setModifiers(int keyCode, boolean isDown){
}
}

//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
Expand Down Expand Up @@ -199,12 +214,17 @@ 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();
}

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);
Expand Down
1 change: 1 addition & 0 deletions app_pojavlauncher/src/main/jni/environ/environ.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
56 changes: 38 additions & 18 deletions app_pojavlauncher/src/main/jni/input_bridge_v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
* - Implements glfwSetCursorPos() to handle grab camera pos correctly.
*/

#include <assert.h>
#include <dlfcn.h>
#include <jni.h>
Expand All @@ -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);
Expand All @@ -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...");
Expand Down Expand Up @@ -71,7 +95,7 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) {
registerFunctions(env);
}
pojav_environ->isGrabbing = JNI_FALSE;

return JNI_VERSION_1_4;
}

Expand Down Expand Up @@ -361,7 +385,7 @@ JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNI
(*pojav_environ->dalvikJavaVMPtr)->AttachCurrentThread(pojav_environ->dalvikJavaVMPtr, &dalvikEnv, NULL);
assert(dalvikEnv != NULL);
assert(pojav_environ->bridgeClazz != NULL);

LOGD("Clipboard: Converting string\n");
char *copySrcC;
jstring copyDst = NULL;
Expand All @@ -374,7 +398,7 @@ JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNI
jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, pojav_environ->bridgeClazz, pojav_environ->method_accessAndroidClipboard, action, copyDst));

if (copySrc) {
(*dalvikEnv)->DeleteLocalRef(dalvikEnv, copyDst);
(*dalvikEnv)->DeleteLocalRef(dalvikEnv, copyDst);
(*env)->ReleaseByteArrayElements(env, copySrc, (jbyte *)copySrcC, 0);
}
(*pojav_environ->dalvikJavaVMPtr)->DetachCurrentThread(pojav_environ->dalvikJavaVMPtr);
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 0 additions & 6 deletions app_pojavlauncher/src/main/res/xml/pref_control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class CallbackBridge {
public static final int ANDROID_TYPE_GRAB_STATE = 0;

public static final boolean INPUT_DEBUG_ENABLED;

public static boolean sGamepadDirectEnabled;

// TODO send grab state event to Android

Expand All @@ -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);
}
Expand All @@ -50,5 +57,6 @@ public static void sendData(int type, String data) {
public static native void nativeSetGrabbing(boolean grab);
public static native ByteBuffer nativeCreateGamepadButtonBuffer();
public static native ByteBuffer nativeCreateGamepadAxisBuffer();
private static native boolean nativeEnableGamepadDirectInput();
}

1 change: 1 addition & 0 deletions jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,7 @@ public static void glfwRequestWindowAttention(@NativeType("GLFWwindow *") long w

public static boolean glfwJoystickPresent(int jid) {
if(jid == GLFW_JOYSTICK_1) {
CallbackBridge.enableGamepadDirectInput();
return true;
}else return false;
}
Expand Down

0 comments on commit f0005fa

Please sign in to comment.