Implement grab listening and use it for Android native mouse grab and Touchpad

This commit is contained in:
artdeell 2022-11-17 20:28:34 +03:00
parent 5d2f61c2db
commit c0e4d45ff3
6 changed files with 92 additions and 43 deletions

View File

@ -0,0 +1,5 @@
package net.kdt.pojavlaunch;
public interface GrabListener {
void onGrabState(boolean isGrabbing);
}

View File

@ -81,6 +81,8 @@ public class MainActivity extends BaseActivity {
MCOptionUtils.load(Tools.getGameDirPath(minecraftProfile));
GameService.startService(this);
initLayout(R.layout.activity_basemain);
CallbackBridge.addGrabListener(touchpad);
CallbackBridge.addGrabListener(minecraftGLView);
// Enabling this on TextureView results in a broken white result
if(PREF_USE_ALTERNATE_SURFACE)
@ -260,6 +262,13 @@ public class MainActivity extends BaseActivity {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
CallbackBridge.removeGrabListener(touchpad);
CallbackBridge.removeGrabListener(minecraftGLView);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);

View File

@ -43,7 +43,8 @@ import org.lwjgl.glfw.CallbackBridge;
/**
* Class dealing with showing minecraft surface and taking inputs to dispatch them to minecraft
*/
public class MinecraftGLSurface extends View {
public class MinecraftGLSurface extends View implements GrabListener{
Handler uiThreadHandler = new Handler();
/* Gamepad object for gamepad inputs, instantiated on need */
private Gamepad mGamepad = null;
/* Pointer Debug textview, used to show info about the pointer state */
@ -410,12 +411,6 @@ public class MinecraftGLSurface extends View {
break;
}
if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice!
if(CallbackBridge.isGrabbing()) {
if(MainActivity.isAndroid8OrHigher() && !hasPointerCapture()){
requestFocus();
requestPointerCapture();
}
}
switch(event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_MOVE:
CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * mScaleFactor);
@ -444,10 +439,6 @@ public class MinecraftGLSurface extends View {
public boolean dispatchCapturedPointerEvent(MotionEvent e) {
CallbackBridge.mouseX += (e.getX()* mScaleFactor);
CallbackBridge.mouseY += (e.getY()* mScaleFactor);
if(!CallbackBridge.isGrabbing()){
releasePointerCapture();
clearFocus();
}
if (mPointerDebugTextView.getVisibility() == View.VISIBLE && !debugErrored) {
StringBuilder builder = new StringBuilder();
@ -653,6 +644,25 @@ public class MinecraftGLSurface extends View {
}, "JVM Main thread").start();
}
@Override
public void onGrabState(boolean isGrabbing) {
uiThreadHandler.post(()->updateGrabState(isGrabbing));
}
private void updateGrabState(boolean isGrabbing) {
if(MainActivity.isAndroid8OrHigher()) {
if (isGrabbing && !hasPointerCapture()) {
requestFocus();
requestPointerCapture();
}
if (!isGrabbing && hasPointerCapture()) {
releasePointerCapture();
clearFocus();
}
}
}
/** A small interface called when the listener is ready for the first time */
public interface SurfaceReadyListener {
void isReady();

View File

@ -6,6 +6,7 @@ import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
@ -25,13 +26,14 @@ import org.lwjgl.glfw.CallbackBridge;
/**
* Class dealing with the virtual mouse
*/
public class Touchpad extends FrameLayout {
public class Touchpad extends FrameLayout implements GrabListener{
/* Whether the Touchpad should be displayed */
private boolean mDisplayState;
/* Mouse pointer icon used by the touchpad */
private final ImageView mMousePointerImageView = new ImageView(getContext());
/* Detect a classic android Tap */
private final GestureDetector mSingleTapDetector = new GestureDetector(getContext(), new SingleTapConfirm());
private final Handler uiThreadHandler = new Handler();
/* Resolution scaler option, allow downsizing a window */
private final float mScaleFactor = DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
/* Current pointer ID to move the mouse */
@ -41,19 +43,6 @@ public class Touchpad extends FrameLayout {
/* Last first pointer positions non-scaled, used to scroll distance */
private float mScrollLastInitialX, mScrollLastInitialY;
/* Handler and message to check if we are grabbing */
private final Runnable mGrabRunnable = new Runnable() {
@Override
public void run() {
if (!CallbackBridge.isGrabbing() && mDisplayState && getVisibility() != VISIBLE) {
enable();
}else{
if ((CallbackBridge.isGrabbing() && getVisibility() != View.GONE) || !mDisplayState && getVisibility() == VISIBLE) {
disable();
}
}
postDelayed(this, 250);
}
};
public Touchpad(@NonNull Context context) {
this(context, null);
@ -156,6 +145,10 @@ public class Touchpad extends FrameLayout {
/** @return The new state, enabled or disabled */
public boolean switchState(){
mDisplayState = !mDisplayState;
if(!CallbackBridge.isGrabbing()) {
if(mDisplayState) enable();
else disable();
}
return mDisplayState;
}
@ -184,7 +177,18 @@ public class Touchpad extends FrameLayout {
// When the game is grabbing, we should not display the mouse
disable();
mDisplayState = false;
postDelayed(mGrabRunnable, 250);
}
@Override
public void onGrabState(boolean isGrabbing) {
uiThreadHandler.post(()->updateGrabState(isGrabbing));
}
private void updateGrabState(boolean isGrabbing) {
if(!isGrabbing) {
if(mDisplayState && getVisibility() != VISIBLE) enable();
if(!mDisplayState && getVisibility() == VISIBLE) disable();
}else{
if(getVisibility() != View.GONE) disable();
}
}
}

View File

@ -2,12 +2,17 @@ package org.lwjgl.glfw;
import net.kdt.pojavlaunch.*;
import android.content.*;
import android.telecom.Call;
import android.view.Choreographer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class CallbackBridge {
public static Choreographer sChoreographer = Choreographer.getInstance();
private static boolean isGrabbing = false;
private static long lastGrabTime = System.currentTimeMillis();
private static final ArrayList<GrabListener> grabListeners = new ArrayList<>();
public static final int ANDROID_TYPE_GRAB_STATE = 0;
public static final int CLIPBOARD_COPY = 2000;
@ -118,11 +123,6 @@ public class CallbackBridge {
public static boolean isGrabbing() {
// Avoid going through the JNI each time.
long currentTime = System.currentTimeMillis();
if (currentTime - lastGrabTime > 250){
isGrabbing = nativeIsGrabbing();
lastGrabTime = currentTime;
}
return isGrabbing;
}
@ -206,6 +206,24 @@ public class CallbackBridge {
}
}
private static void onGrabStateChanged(boolean grabbing) {
isGrabbing = grabbing;
synchronized (grabListeners) {
for (GrabListener g : grabListeners) g.onGrabState(grabbing);
}
}
public static void addGrabListener(GrabListener listener) {
synchronized (grabListeners) {
listener.onGrabState(isGrabbing);
grabListeners.add(listener);
}
}
public static void removeGrabListener(GrabListener listener) {
synchronized (grabListeners) {
grabListeners.remove(listener);
}
}
public static native void nativeSetUseInputStackQueue(boolean useInputStackQueue);
public static native boolean nativeAttachThreadToOther(boolean isAndroid, boolean isUsePushPoll);

View File

@ -46,7 +46,11 @@ static float grabCursorX, grabCursorY, lastCursorX, lastCursorY;
jclass inputBridgeClass_ANDROID, inputBridgeClass_JRE;
jmethodID inputBridgeMethod_ANDROID, inputBridgeMethod_JRE;
jmethodID method_accessAndroidClipboard;
jmethodID method_onGrabStateChanged;
jmethodID method_glftSetWindowAttrib;
jclass bridgeClazz;
jclass vmGlfwClass;
jboolean isGrabbing;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
@ -55,11 +59,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
dalvikJavaVMPtr = vm;
(*vm)->GetEnv(vm, (void**) &dalvikJNIEnvPtr_ANDROID, JNI_VERSION_1_4);
bridgeClazz = (*dalvikJNIEnvPtr_ANDROID)->NewGlobalRef(dalvikJNIEnvPtr_ANDROID,(*dalvikJNIEnvPtr_ANDROID) ->FindClass(dalvikJNIEnvPtr_ANDROID,"org/lwjgl/glfw/CallbackBridge"));
assert(bridgeClazz != NULL);
method_accessAndroidClipboard = (*dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(dalvikJNIEnvPtr_ANDROID, bridgeClazz, "accessAndroidClipboard", "(ILjava/lang/String;)Ljava/lang/String;");
method_onGrabStateChanged = (*dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(dalvikJNIEnvPtr_ANDROID, bridgeClazz, "onGrabStateChanged", "(Z)V");
isUseStackQueueCall = JNI_FALSE;
} else if (dalvikJavaVMPtr != vm) {
runtimeJavaVMPtr = vm;
(*vm)->GetEnv(vm, (void**) &runtimeJNIEnvPtr_JRE, JNI_VERSION_1_4);
vmGlfwClass = (*runtimeJNIEnvPtr_JRE)->NewGlobalRef(runtimeJNIEnvPtr_JRE, (*runtimeJNIEnvPtr_JRE)->FindClass(runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW"));
method_glftSetWindowAttrib = (*runtimeJNIEnvPtr_JRE)->GetStaticMethodID(runtimeJNIEnvPtr_JRE, vmGlfwClass, "glfwSetWindowAttrib", "(JII)V");
hookExec();
}
@ -225,9 +232,6 @@ JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNI
(*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL);
assert(dalvikEnv != NULL);
assert(bridgeClazz != NULL);
LOGD("Clipboard: Obtaining method\n");
jmethodID bridgeMethod = (*dalvikEnv)->GetStaticMethodID(dalvikEnv, bridgeClazz, "accessAndroidClipboard", "(ILjava/lang/String;)Ljava/lang/String;");
assert(bridgeMethod != NULL);
LOGD("Clipboard: Converting string\n");
char *copySrcC = NULL;
@ -238,7 +242,7 @@ JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNI
}
LOGD("Clipboard: Calling 2nd\n");
jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, bridgeClazz, bridgeMethod, action, copyDst));
jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, bridgeClazz, method_accessAndroidClipboard, action, copyDst));
if (copySrc) {
(*dalvikEnv)->DeleteLocalRef(dalvikEnv, copyDst);
@ -257,6 +261,10 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetInputRead
}
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetGrabbing(JNIEnv* env, jclass clazz, jboolean grabbing, jint xset, jint yset) {
JNIEnv *dalvikEnv;
(*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL);
(*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, bridgeClazz, method_onGrabStateChanged, grabbing);
(*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr);
isGrabbing = grabbing;
if (isGrabbing == JNI_TRUE) {
grabCursorX = xset; // savedWidth / 2;
@ -422,14 +430,9 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(
return;
}
jclass glfwClazz = (*runtimeJNIEnvPtr_JRE)->FindClass(runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW");
assert(glfwClazz != NULL);
jmethodID glfwMethod = (*runtimeJNIEnvPtr_JRE)->GetStaticMethodID(runtimeJNIEnvPtr_JRE, glfwClazz, "glfwSetWindowAttrib", "(JII)V");
assert(glfwMethod != NULL);
(*runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(
runtimeJNIEnvPtr_JRE,
glfwClazz, glfwMethod,
vmGlfwClass, method_glftSetWindowAttrib,
(jlong) showingWindow, attrib, value
);
}