diff --git a/build.gradle b/build.gradle index 4709e3c0..e642e80d 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ android { compileSdkVersion = "android-32" defaultConfig { minSdkVersion(29) + multiDexEnabled true ndk.stl = "c++_shared" } @@ -30,7 +31,7 @@ android { } build { - finalizedBy(':wrapper:launcher:build') + //finalizedBy(':wrapper:launcher:build') } dependencies { @@ -48,6 +49,8 @@ dependencies { implementation("commons-io:commons-io:2.13.0") implementation("commons-codec:commons-codec:1.15") implementation("androidx.annotation:annotation:1.7.1") + implementation("androidx.core:core:1.13.1") implementation("com.microsoft.azure:msal4j:1.14.0") + implementation("com.github.Mathias-Boulay:android_gamepad_remapper:2.0.3") implementation("blank:unity-classes") } diff --git a/settings.gradle b/settings.gradle index 7d680692..0dd86d73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,7 +20,7 @@ dependencyResolutionManagement { google() mavenCentral() maven { - url = uri("https://repo.u-team.info") + url = uri("https://jitpack.io") } flatDir { dirs("libs") @@ -30,5 +30,5 @@ dependencyResolutionManagement { } rootProject.name = "Pojlib" -include ':wrapper', ':wrapper:launcher', "wrapper:unityLibrary", "wrapper:unityLibrary:xrmanifest.androidlib", ":jre_lwjgl3glfw" +include ":jre_lwjgl3glfw", ':wrapper', ':wrapper:launcher', "wrapper:unityLibrary", "wrapper:unityLibrary:xrmanifest.androidlib" diff --git a/src/main/assets/lwjgl/lwjgl-glfw-classes.jar b/src/main/assets/lwjgl/lwjgl-glfw-classes.jar index e575b75f..57bcc320 100644 Binary files a/src/main/assets/lwjgl/lwjgl-glfw-classes.jar and b/src/main/assets/lwjgl/lwjgl-glfw-classes.jar differ diff --git a/src/main/assets/lwjgl/version b/src/main/assets/lwjgl/version index 2561c070..810b85e0 100644 --- a/src/main/assets/lwjgl/version +++ b/src/main/assets/lwjgl/version @@ -1 +1 @@ -1726431237721 \ No newline at end of file +1725986487354 \ No newline at end of file diff --git a/src/main/java/dalvik/annotation/optimization/CriticalNative.java b/src/main/java/dalvik/annotation/optimization/CriticalNative.java new file mode 100644 index 00000000..0ffea502 --- /dev/null +++ b/src/main/java/dalvik/annotation/optimization/CriticalNative.java @@ -0,0 +1,12 @@ +package dalvik.annotation.optimization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +// Dummy CriticalNative annotation. On devices which dont have it this declaration will prevent errors. +// On devices that do have it it will be overridden by the system one and work as usual +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface CriticalNative { +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/glfw/CallbackBridge.java b/src/main/java/org/lwjgl/glfw/CallbackBridge.java index f2c0ae9b..fc419669 100644 --- a/src/main/java/org/lwjgl/glfw/CallbackBridge.java +++ b/src/main/java/org/lwjgl/glfw/CallbackBridge.java @@ -1,29 +1,37 @@ package org.lwjgl.glfw; -import android.os.Handler; -import android.os.Looper; +import android.content.*; +import android.view.Choreographer; + +import androidx.annotation.Nullable; + +import java.util.ArrayList; + +import dalvik.annotation.optimization.CriticalNative; +import pojlib.UnityPlayerActivity; +import pojlib.input.GrabListener; +import pojlib.input.LwjglGlfwKeycode; public class CallbackBridge { - public static final int ANDROID_TYPE_GRAB_STATE = 0; - + public static final Choreographer sChoreographer = Choreographer.getInstance(); + private static boolean isGrabbing = false; + private static final ArrayList grabListeners = new ArrayList<>(); + public static final int CLIPBOARD_COPY = 2000; public static final int CLIPBOARD_PASTE = 2001; - + public static final int CLIPBOARD_OPEN = 2002; + + public static volatile int windowWidth, windowHeight; public static volatile int physicalWidth, physicalHeight; public static float mouseX, mouseY; - public static StringBuilder DEBUG_STRING = new StringBuilder(); - private static boolean threadAttached; public volatile static boolean holdingAlt, holdingCapslock, holdingCtrl, holdingNumlock, holdingShift; - public static void putMouseEventWithCoords(int button, float x, float y) { putMouseEventWithCoords(button, true, x, y); - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(() -> putMouseEventWithCoords(button, false, x, y), 22); - + sChoreographer.postFrameCallbackDelayed(l -> putMouseEventWithCoords(button, false, x, y), 33); } - + public static void putMouseEventWithCoords(int button, boolean isDown, float x, float y /* , int dz, long nanos */) { sendCursorPos(x, y); sendMouseKeycode(button, CallbackBridge.getCurrentMods(), isDown); @@ -31,23 +39,13 @@ public static void putMouseEventWithCoords(int button, boolean isDown, float x, public static void sendCursorPos(float x, float y) { - if (!threadAttached) { - threadAttached = CallbackBridge.nativeAttachThreadToOther(true, true); - } - - DEBUG_STRING.append("CursorPos=").append(x).append(", ").append(y).append("\n"); mouseX = x; mouseY = y; nativeSendCursorPos(mouseX, mouseY); } - - public static void sendPrepareGrabInitialPos() { - DEBUG_STRING.append("Prepare set grab initial posititon: ignored"); - } public static void sendKeycode(int keycode, char keychar, int scancode, int modifiers, boolean isDown) { - DEBUG_STRING.append("KeyCode=").append(keycode).append(", Char=").append(keychar); - + // TODO CHECK: This may cause input issue, not receive input! if(keycode != 0) nativeSendKey(keycode,scancode,isDown ? 1 : 0, modifiers); if(isDown && keychar != '\u0000') { nativeSendCharMods(keychar,modifiers); @@ -82,7 +80,7 @@ public static void sendMouseButton(int button, boolean status) { } public static void sendMouseKeycode(int button, int modifiers, boolean isDown) { - DEBUG_STRING.append("MouseKey=").append(button).append(", down=").append(isDown).append("\n"); + // if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n"); nativeSendMouseButton(button, isDown ? 1 : 0, modifiers); } @@ -90,24 +88,34 @@ public static void sendMouseKeycode(int keycode) { sendMouseKeycode(keycode, CallbackBridge.getCurrentMods(), true); sendMouseKeycode(keycode, CallbackBridge.getCurrentMods(), false); } - + public static void sendScroll(double xoffset, double yoffset) { - DEBUG_STRING.append("ScrollX=").append(xoffset).append(",ScrollY=").append(yoffset); nativeSendScroll(xoffset, yoffset); } + public static void sendUpdateWindowSize(int w, int h) { + nativeSendScreenSize(w, h); + } + public static boolean isGrabbing() { - return nativeIsGrabbing(); + // Avoid going through the JNI each time. + return isGrabbing; } // Called from JRE side - public static String accessAndroidClipboard(int type, String copy) { + @SuppressWarnings("unused") + public static @Nullable String accessAndroidClipboard(int type, String copy) { switch (type) { case CLIPBOARD_COPY: + UnityPlayerActivity.GLOBAL_CLIPBOARD.setPrimaryClip(ClipData.newPlainText("Copy", copy)); return null; case CLIPBOARD_PASTE: - return ""; + if (UnityPlayerActivity.GLOBAL_CLIPBOARD.hasPrimaryClip() && UnityPlayerActivity.GLOBAL_CLIPBOARD.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { + return UnityPlayerActivity.GLOBAL_CLIPBOARD.getPrimaryClip().getItemAt(0).getText().toString(); + } else { + return ""; + } default: return null; } @@ -115,24 +123,86 @@ public static String accessAndroidClipboard(int type, String copy) { public static int getCurrentMods() { - return 0; + int currMods = 0; + if (holdingAlt) { + currMods |= LwjglGlfwKeycode.GLFW_MOD_ALT; + } if (holdingCapslock) { + currMods |= LwjglGlfwKeycode.GLFW_MOD_CAPS_LOCK; + } if (holdingCtrl) { + currMods |= LwjglGlfwKeycode.GLFW_MOD_CONTROL; + } if (holdingNumlock) { + currMods |= LwjglGlfwKeycode.GLFW_MOD_NUM_LOCK; + } if (holdingShift) { + currMods |= LwjglGlfwKeycode.GLFW_MOD_SHIFT; + } + return currMods; } - public static native boolean nativeAttachThreadToOther(boolean isAndroid, boolean isUsePushPoll); + public static void setModifiers(int keyCode, boolean isDown){ + switch (keyCode){ + case LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT: + CallbackBridge.holdingShift = isDown; + return; + + case LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL: + CallbackBridge.holdingCtrl = isDown; + return; + + case LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT: + CallbackBridge.holdingAlt = isDown; + return; - private static native boolean nativeSendChar(char codepoint); + case LwjglGlfwKeycode.GLFW_KEY_CAPS_LOCK: + CallbackBridge.holdingCapslock = isDown; + return; + + case LwjglGlfwKeycode.GLFW_KEY_NUM_LOCK: + CallbackBridge.holdingNumlock = isDown; + } + } + + //Called from JRE side + @SuppressWarnings("unused") + private static void onGrabStateChanged(final boolean grabbing) { + isGrabbing = grabbing; + sChoreographer.postFrameCallbackDelayed((time) -> { + // If the grab re-changed, skip notify process + if(isGrabbing != grabbing) return; + + System.out.println("Grab changed : " + grabbing); + synchronized (grabListeners) { + for (GrabListener g : grabListeners) g.onGrabState(grabbing); + } + + }, 16); + + } + 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); + } + } + + @CriticalNative + public static native void nativeSetUseInputStackQueue(boolean useInputStackQueue); + + @CriticalNative private static native boolean nativeSendChar(char codepoint); // GLFW: GLFWCharModsCallback deprecated, but is Minecraft still use? - private static native boolean nativeSendCharMods(char codepoint, int mods); - private static native void nativeSendKey(int key, int scancode, int action, int mods); + @CriticalNative private static native boolean nativeSendCharMods(char codepoint, int mods); + @CriticalNative private static native void nativeSendKey(int key, int scancode, int action, int mods); // private static native void nativeSendCursorEnter(int entered); - private static native void nativeSendCursorPos(float x, float y); - private static native void nativeSendMouseButton(int button, int action, int mods); - private static native void nativeSendScroll(double xoffset, double yoffset); - private static native void nativeSendScreenSize(int width, int height); - - public static native boolean nativeIsGrabbing(); + @CriticalNative private static native void nativeSendCursorPos(float x, float y); + @CriticalNative private static native void nativeSendMouseButton(int button, int action, int mods); + @CriticalNative private static native void nativeSendScroll(double xoffset, double yoffset); + @CriticalNative private static native void nativeSendScreenSize(int width, int height); + public static native void nativeSetWindowAttrib(int attrib, int value); static { System.loadLibrary("pojavexec"); } } - diff --git a/src/main/java/pojlib/API.java b/src/main/java/pojlib/API.java index 65e2bf59..00f5373a 100644 --- a/src/main/java/pojlib/API.java +++ b/src/main/java/pojlib/API.java @@ -36,6 +36,7 @@ public class API { public static String memoryValue = "1800"; public static boolean developerMods; public static MinecraftAccount currentAcc; + public static MinecraftInstances.Instance currentInstance; public static boolean advancedDebugger; diff --git a/src/main/java/pojlib/InstanceHandler.java b/src/main/java/pojlib/InstanceHandler.java index a5e424de..543e973d 100644 --- a/src/main/java/pojlib/InstanceHandler.java +++ b/src/main/java/pojlib/InstanceHandler.java @@ -288,6 +288,7 @@ public static boolean delete(MinecraftInstances instances, MinecraftInstances.In public static void launchInstance(Activity activity, MinecraftAccount account, MinecraftInstances.Instance instance) { try { + API.currentInstance = instance; JREUtils.redirectAndPrintJRELog(); VLoader.setAndroidInitInfo(activity); JREUtils.launchJavaVM(activity, instance.generateLaunchArgs(account), instance); diff --git a/src/main/java/pojlib/UnityPlayerActivity.java b/src/main/java/pojlib/UnityPlayerActivity.java index ac65c4e8..b3d84f4b 100644 --- a/src/main/java/pojlib/UnityPlayerActivity.java +++ b/src/main/java/pojlib/UnityPlayerActivity.java @@ -1,11 +1,23 @@ package pojlib; +import static android.os.Build.VERSION.SDK_INT; + +import static org.lwjgl.glfw.CallbackBridge.sendKeyPress; +import static org.lwjgl.glfw.CallbackBridge.sendMouseButton; + +import android.annotation.SuppressLint; +import android.app.Activity; import android.app.Activity; import android.app.ActivityGroup; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Intent; import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import android.os.PowerManager; +import android.util.DisplayMetrics; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Window; @@ -14,17 +26,34 @@ import com.unity3d.player.IUnityPlayerLifecycleEvents; import com.unity3d.player.UnityPlayer; +import org.lwjgl.glfw.CallbackBridge; + import java.io.File; import java.io.IOException; import java.util.Objects; +import fr.spse.gamepad_remapper.RemapperManager; +import fr.spse.gamepad_remapper.RemapperView; +import pojlib.input.AWTInputBridge; +import pojlib.input.EfficientAndroidLWJGLKeycode; +import pojlib.input.GrabListener; +import pojlib.input.LwjglGlfwKeycode; +import pojlib.input.gamepad.DefaultDataProvider; +import pojlib.input.gamepad.Gamepad; import pojlib.util.Constants; import pojlib.util.FileUtil; +import pojlib.util.Logger; -public class UnityPlayerActivity extends ActivityGroup implements IUnityPlayerLifecycleEvents +public class UnityPlayerActivity extends ActivityGroup implements IUnityPlayerLifecycleEvents, GrabListener { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code + public static volatile ClipboardManager GLOBAL_CLIPBOARD; private PowerManager.WakeLock wakeLock; + private Gamepad mGamepad = null; + + private RemapperManager mInputManager; + + private boolean mLastGrabState = false; // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player // The command line arguments are passed as a string, separated by spaces @@ -70,6 +99,27 @@ protected String updateUnityCommandLineArguments(String cmdLine) } catch (IOException e) { throw new RuntimeException(e); } + + updateWindowSize(this); + GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + mInputManager = new RemapperManager(this, new RemapperView.Builder(null) + .remapA(true) + .remapB(true) + .remapX(true) + .remapY(true) + + .remapLeftJoystick(true) + .remapRightJoystick(true) + .remapStart(true) + .remapSelect(true) + .remapLeftShoulder(true) + .remapRightShoulder(true) + .remapLeftTrigger(true) + .remapRightTrigger(true) + .remapDpad(true)); + + CallbackBridge.nativeSetUseInputStackQueue(true); } public static String installLWJGL(Activity activity) throws IOException { @@ -87,7 +137,220 @@ public static String installLWJGL(Activity activity) throws IOException { return lwjgl.getAbsolutePath(); } + public static DisplayMetrics getDisplayMetrics(Activity activity) { + DisplayMetrics displayMetrics = new DisplayMetrics(); + + if(activity.isInMultiWindowMode() || activity.isInPictureInPictureMode()){ + //For devices with free form/split screen, we need window size, not screen size. + displayMetrics = activity.getResources().getDisplayMetrics(); + }else{ + if (SDK_INT >= Build.VERSION_CODES.R) { + activity.getDisplay().getRealMetrics(displayMetrics); + } else { // Removed the clause for devices with unofficial notch support, since it also ruins all devices with virtual nav bars before P + activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); + } + } + currentDisplayMetrics = displayMetrics; + return displayMetrics; + } + + public static DisplayMetrics currentDisplayMetrics; + + public static void updateWindowSize(Activity activity) { + currentDisplayMetrics = getDisplayMetrics(activity); + + CallbackBridge.physicalWidth = currentDisplayMetrics.widthPixels; + CallbackBridge.physicalHeight = currentDisplayMetrics.heightPixels; + } + + public static float dpToPx(float dp) { + //Better hope for the currentDisplayMetrics to be good + return dp * currentDisplayMetrics.density; + } + + public static float pxToDp(float px){ + //Better hope for the currentDisplayMetrics to be good + return px / currentDisplayMetrics.density; + } + + public static void querySystemClipboard() { + ClipData clipData = GLOBAL_CLIPBOARD.getPrimaryClip(); + if(clipData == null) { + AWTInputBridge.nativeClipboardReceived(null, null); + return; + } + ClipData.Item firstClipItem = clipData.getItemAt(0); + //TODO: coerce to HTML if the clip item is styled + CharSequence clipItemText = firstClipItem.getText(); + if(clipItemText == null) { + AWTInputBridge.nativeClipboardReceived(null, null); + return; + } + AWTInputBridge.nativeClipboardReceived(clipItemText.toString(), "plain"); + } + + public static void putClipboardData(String data, String mimeType) { + ClipData clipData = null; + switch(mimeType) { + case "text/plain": + clipData = ClipData.newPlainText("AWT Paste", data); + break; + case "text/html": + clipData = ClipData.newHtmlText("AWT Paste", data, data); + } + if(clipData != null) GLOBAL_CLIPBOARD.setPrimaryClip(clipData); + } + + private void createGamepad(InputDevice inputDevice) { + mGamepad = new Gamepad(inputDevice, DefaultDataProvider.INSTANCE); + } + + @SuppressLint("NewApi") @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + int mouseCursorIndex = -1; + + if(Gamepad.isGamepadEvent(event)){ + if(mGamepad == null) createGamepad(event.getDevice()); + + mInputManager.handleMotionEventInput(this, event, mGamepad); + return true; + } + + for(int i = 0; i < event.getPointerCount(); i++) { + if(event.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE && event.getToolType(i) != MotionEvent.TOOL_TYPE_STYLUS ) continue; + // Mouse found + mouseCursorIndex = i; + break; + } + if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice! + + // Make sure we grabbed the mouse if necessary + updateGrabState(CallbackBridge.isGrabbing()); + + switch(event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_MOVE: + CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * 100); + CallbackBridge.mouseY = (event.getY(mouseCursorIndex) * 100); + CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); + return true; + case MotionEvent.ACTION_SCROLL: + CallbackBridge.sendScroll((double) event.getAxisValue(MotionEvent.AXIS_HSCROLL), (double) event.getAxisValue(MotionEvent.AXIS_VSCROLL)); + return true; + case MotionEvent.ACTION_BUTTON_PRESS: + return sendMouseButtonUnconverted(event.getActionButton(),true); + case MotionEvent.ACTION_BUTTON_RELEASE: + return sendMouseButtonUnconverted(event.getActionButton(),false); + default: + return false; + } + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + boolean handleEvent; + if(!(handleEvent = processKeyEvent(event))) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if(event.getAction() != KeyEvent.ACTION_UP) return true; // We eat it anyway + sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_ESCAPE); + return true; + } + } + return handleEvent; + } + + /** The event for keyboard/ gamepad button inputs */ + public boolean processKeyEvent(KeyEvent event) { + // Logger.getInstance().appendToLog("KeyEvent " + event.toString()); + + //Filtering useless events by order of probability + int eventKeycode = event.getKeyCode(); + if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true; + if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false; + if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false; + if(event.getRepeatCount() != 0) return true; + int action = event.getAction(); + if(action == KeyEvent.ACTION_MULTIPLE) return true; + // Ignore the cancelled up events. They occur when the user switches layouts. + // In accordance with https://developer.android.com/reference/android/view/KeyEvent#FLAG_CANCELED + if(action == KeyEvent.ACTION_UP && + (event.getFlags() & KeyEvent.FLAG_CANCELED) != 0) return true; + + //Sometimes, key events comes from SOME keys of the software keyboard + //Even weirder, is is unknown why a key or another is selected to trigger a keyEvent + if((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD){ + if(eventKeycode == KeyEvent.KEYCODE_ENTER) return true; //We already listen to it. + dispatchKeyEvent(event); + return true; + } + + //Sometimes, key events may come from the mouse + if(event.getDevice() != null + && ( (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE + || (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ){ + + if(eventKeycode == KeyEvent.KEYCODE_BACK){ + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, event.getAction() == KeyEvent.ACTION_DOWN); + return true; + } + } + + if(Gamepad.isGamepadEvent(event)){ + if(mGamepad == null) createGamepad(event.getDevice()); + + mInputManager.handleKeyEventInput(this, event, mGamepad); + return true; + } + + int index = EfficientAndroidLWJGLKeycode.getIndexByKey(eventKeycode); + if(EfficientAndroidLWJGLKeycode.containsIndex(index)) { + EfficientAndroidLWJGLKeycode.execKey(event, index); + return true; + } + + // Some events will be generated an infinite number of times when no consumed + return (event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK; + } + + /** Convert the mouse button, then send it + * @return Whether the event was processed + */ + public static boolean sendMouseButtonUnconverted(int button, boolean status) { + int glfwButton = -256; + switch (button) { + case MotionEvent.BUTTON_PRIMARY: + glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT; + break; + case MotionEvent.BUTTON_TERTIARY: + glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_MIDDLE; + break; + case MotionEvent.BUTTON_SECONDARY: + glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT; + break; + } + if(glfwButton == -256) return false; + sendMouseButton(glfwButton, status); + return true; + } + + @Override + public void onGrabState(boolean isGrabbing) { + mUnityPlayer.post(()->updateGrabState(isGrabbing)); + } + + // private TouchEventProcessor pickEventProcessor(boolean isGrabbing) { + // return isGrabbing ? mIngameProcessor : mInGUIProcessor; + // } + + private void updateGrabState(boolean isGrabbing) { + // if(mLastGrabState != isGrabbing) { + // mCurrentTouchProcessor.cancelPendingActions(); + // mCurrentTouchProcessor = pickEventProcessor(isGrabbing); + // mLastGrabState = isGrabbing; + // } + } + + @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return super.onKeyMultiple(keyCode, repeatCount, event); } @@ -184,14 +447,14 @@ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { mUnityPlayer.windowFocusChanged(hasFocus); } - // For some reason the multiple keyevent type is not supported by the ndk. +/* // For some reason the multiple keyevent type is not supported by the ndk. // Force event injection by overriding dispatchKeyEvent(). @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_MULTIPLE) return mUnityPlayer.injectEvent(event); return super.dispatchKeyEvent(event); - } + }*/ // Pass any events not handled by (unfocused) views straight to UnityPlayer @Override public boolean onKeyUp(int keyCode, KeyEvent event) { diff --git a/src/main/java/pojlib/input/AWTInputBridge.java b/src/main/java/pojlib/input/AWTInputBridge.java new file mode 100644 index 00000000..c4f44297 --- /dev/null +++ b/src/main/java/pojlib/input/AWTInputBridge.java @@ -0,0 +1,44 @@ +package pojlib.input; + +public class AWTInputBridge { + public static final int EVENT_TYPE_CHAR = 1000; + public static final int EVENT_TYPE_CURSOR_POS = 1003; + public static final int EVENT_TYPE_KEY = 1005; + public static final int EVENT_TYPE_MOUSE_BUTTON = 1006; + + public static void sendKey(char keychar, int keycode) { + // TODO: Android -> AWT keycode mapping + nativeSendData(EVENT_TYPE_KEY, (int) keychar, keycode, 1, 0); + nativeSendData(EVENT_TYPE_KEY, (int) keychar, keycode, 0, 0); + } + + public static void sendKey(char keychar, int keycode, int state) { + // TODO: Android -> AWT keycode mapping + nativeSendData(EVENT_TYPE_KEY, (int) keychar, keycode, state, 0); + } + + public static void sendChar(char keychar){ + nativeSendData(EVENT_TYPE_CHAR, (int) keychar, 0, 0, 0); + } + + public static void sendMousePress(int awtButtons, boolean isDown) { + nativeSendData(EVENT_TYPE_MOUSE_BUTTON, awtButtons, isDown ? 1 : 0, 0, 0); + } + + public static void sendMousePress(int awtButtons) { + sendMousePress(awtButtons, true); + sendMousePress(awtButtons, false); + } + + public static void sendMousePos(int x, int y) { + nativeSendData(EVENT_TYPE_CURSOR_POS, x, y, 0, 0); + } + + static { + System.loadLibrary("pojavexec_awt"); + } + + public static native void nativeSendData(int type, int i1, int i2, int i3, int i4); + public static native void nativeClipboardReceived(String data, String mimeTypeSub); + public static native void nativeMoveWindow(int xoff, int yoff); +} \ No newline at end of file diff --git a/src/main/java/pojlib/input/CriticalNativeTest.java b/src/main/java/pojlib/input/CriticalNativeTest.java new file mode 100644 index 00000000..70c56eee --- /dev/null +++ b/src/main/java/pojlib/input/CriticalNativeTest.java @@ -0,0 +1,11 @@ +package pojlib.input; + +import dalvik.annotation.optimization.CriticalNative; + + public class CriticalNativeTest { + @CriticalNative + public static native void testCriticalNative(int arg0, int arg1); + public static void invokeTest() { + testCriticalNative(0, 0); + } +} diff --git a/src/main/java/pojlib/input/EfficientAndroidLWJGLKeycode.java b/src/main/java/pojlib/input/EfficientAndroidLWJGLKeycode.java new file mode 100644 index 00000000..7653f59a --- /dev/null +++ b/src/main/java/pojlib/input/EfficientAndroidLWJGLKeycode.java @@ -0,0 +1,223 @@ +package pojlib.input; + +import static org.lwjgl.glfw.CallbackBridge.sendKeyPress; + +import android.view.KeyEvent; + +import org.lwjgl.glfw.CallbackBridge; + +import java.util.Arrays; + +public class EfficientAndroidLWJGLKeycode { + + //This old version of this class was using an ArrayMap, a generic Key -> Value data structure. + //The key being the android keycode from a KeyEvent + //The value its LWJGL equivalent. + private static final int KEYCODE_COUNT = 106; + private static final int[] sAndroidKeycodes = new int[KEYCODE_COUNT]; + private static final short[] sLwjglKeycodes = new short[KEYCODE_COUNT]; + private static String[] androidKeyNameArray; /* = new String[androidKeycodes.length]; */ + private static int mTmpCount = 0; + + static { + + /* BINARY SEARCH IS PERFORMED ON THE androidKeycodes ARRAY ! + WHEN ADDING A MAPPING, ADD IT SO THE androidKeycodes ARRAY STAYS SORTED ! */ + // Mapping Android Keycodes to LWJGL Keycodes + add(KeyEvent.KEYCODE_UNKNOWN, LwjglGlfwKeycode.GLFW_KEY_UNKNOWN); + add(KeyEvent.KEYCODE_HOME, LwjglGlfwKeycode.GLFW_KEY_HOME); + // Escape key + add(KeyEvent.KEYCODE_BACK, LwjglGlfwKeycode.GLFW_KEY_ESCAPE); + + // 0-9 keys + add(KeyEvent.KEYCODE_0, LwjglGlfwKeycode.GLFW_KEY_0); //7 + add(KeyEvent.KEYCODE_1, LwjglGlfwKeycode.GLFW_KEY_1); + add(KeyEvent.KEYCODE_2, LwjglGlfwKeycode.GLFW_KEY_2); + add(KeyEvent.KEYCODE_3, LwjglGlfwKeycode.GLFW_KEY_3); + add(KeyEvent.KEYCODE_4, LwjglGlfwKeycode.GLFW_KEY_4); + add(KeyEvent.KEYCODE_5, LwjglGlfwKeycode.GLFW_KEY_5); + add(KeyEvent.KEYCODE_6, LwjglGlfwKeycode.GLFW_KEY_6); + add(KeyEvent.KEYCODE_7, LwjglGlfwKeycode.GLFW_KEY_7); + add(KeyEvent.KEYCODE_8, LwjglGlfwKeycode.GLFW_KEY_8); + add(KeyEvent.KEYCODE_9, LwjglGlfwKeycode.GLFW_KEY_9); //16 + + add(KeyEvent.KEYCODE_POUND, LwjglGlfwKeycode.GLFW_KEY_3); + + // Arrow keys + add(KeyEvent.KEYCODE_DPAD_UP, LwjglGlfwKeycode.GLFW_KEY_UP); //19 + add(KeyEvent.KEYCODE_DPAD_DOWN, LwjglGlfwKeycode.GLFW_KEY_DOWN); + add(KeyEvent.KEYCODE_DPAD_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT); + add(KeyEvent.KEYCODE_DPAD_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT); //22 + + // A-Z keys + add(KeyEvent.KEYCODE_A, LwjglGlfwKeycode.GLFW_KEY_A); //29 + add(KeyEvent.KEYCODE_B, LwjglGlfwKeycode.GLFW_KEY_B); + add(KeyEvent.KEYCODE_C, LwjglGlfwKeycode.GLFW_KEY_C); + add(KeyEvent.KEYCODE_D, LwjglGlfwKeycode.GLFW_KEY_D); + add(KeyEvent.KEYCODE_E, LwjglGlfwKeycode.GLFW_KEY_E); + add(KeyEvent.KEYCODE_F, LwjglGlfwKeycode.GLFW_KEY_F); + add(KeyEvent.KEYCODE_G, LwjglGlfwKeycode.GLFW_KEY_G); + add(KeyEvent.KEYCODE_H, LwjglGlfwKeycode.GLFW_KEY_H); + add(KeyEvent.KEYCODE_I, LwjglGlfwKeycode.GLFW_KEY_I); + add(KeyEvent.KEYCODE_J, LwjglGlfwKeycode.GLFW_KEY_J); + add(KeyEvent.KEYCODE_K, LwjglGlfwKeycode.GLFW_KEY_K); + add(KeyEvent.KEYCODE_L, LwjglGlfwKeycode.GLFW_KEY_L); + add(KeyEvent.KEYCODE_M, LwjglGlfwKeycode.GLFW_KEY_M); + add(KeyEvent.KEYCODE_N, LwjglGlfwKeycode.GLFW_KEY_N); + add(KeyEvent.KEYCODE_O, LwjglGlfwKeycode.GLFW_KEY_O); + add(KeyEvent.KEYCODE_P, LwjglGlfwKeycode.GLFW_KEY_P); + add(KeyEvent.KEYCODE_Q, LwjglGlfwKeycode.GLFW_KEY_Q); + add(KeyEvent.KEYCODE_R, LwjglGlfwKeycode.GLFW_KEY_R); + add(KeyEvent.KEYCODE_S, LwjglGlfwKeycode.GLFW_KEY_S); + add(KeyEvent.KEYCODE_T, LwjglGlfwKeycode.GLFW_KEY_T); + add(KeyEvent.KEYCODE_U, LwjglGlfwKeycode.GLFW_KEY_U); + add(KeyEvent.KEYCODE_V, LwjglGlfwKeycode.GLFW_KEY_V); + add(KeyEvent.KEYCODE_W, LwjglGlfwKeycode.GLFW_KEY_W); + add(KeyEvent.KEYCODE_X, LwjglGlfwKeycode.GLFW_KEY_X); + add(KeyEvent.KEYCODE_Y, LwjglGlfwKeycode.GLFW_KEY_Y); + add(KeyEvent.KEYCODE_Z, LwjglGlfwKeycode.GLFW_KEY_Z); //54 + + + add(KeyEvent.KEYCODE_COMMA, LwjglGlfwKeycode.GLFW_KEY_COMMA); + add(KeyEvent.KEYCODE_PERIOD, LwjglGlfwKeycode.GLFW_KEY_PERIOD); + + // Alt keys + add(KeyEvent.KEYCODE_ALT_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT); + add(KeyEvent.KEYCODE_ALT_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_ALT); + + // Shift keys + add(KeyEvent.KEYCODE_SHIFT_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT); + add(KeyEvent.KEYCODE_SHIFT_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_SHIFT); + + add(KeyEvent.KEYCODE_TAB, LwjglGlfwKeycode.GLFW_KEY_TAB); + add(KeyEvent.KEYCODE_SPACE, LwjglGlfwKeycode.GLFW_KEY_SPACE); + add(KeyEvent.KEYCODE_ENTER, LwjglGlfwKeycode.GLFW_KEY_ENTER); //66 + add(KeyEvent.KEYCODE_DEL, LwjglGlfwKeycode.GLFW_KEY_BACKSPACE); // Backspace + add(KeyEvent.KEYCODE_GRAVE, LwjglGlfwKeycode.GLFW_KEY_GRAVE_ACCENT); + add(KeyEvent.KEYCODE_MINUS, LwjglGlfwKeycode.GLFW_KEY_MINUS); + add(KeyEvent.KEYCODE_EQUALS, LwjglGlfwKeycode.GLFW_KEY_EQUAL); + add(KeyEvent.KEYCODE_LEFT_BRACKET, LwjglGlfwKeycode.GLFW_KEY_LEFT_BRACKET); + add(KeyEvent.KEYCODE_RIGHT_BRACKET, LwjglGlfwKeycode.GLFW_KEY_RIGHT_BRACKET); + add(KeyEvent.KEYCODE_BACKSLASH, LwjglGlfwKeycode.GLFW_KEY_BACKSLASH); + add(KeyEvent.KEYCODE_SEMICOLON, LwjglGlfwKeycode.GLFW_KEY_SEMICOLON); //74 + add(KeyEvent.KEYCODE_APOSTROPHE, LwjglGlfwKeycode.GLFW_KEY_APOSTROPHE); + add(KeyEvent.KEYCODE_SLASH, LwjglGlfwKeycode.GLFW_KEY_SLASH); //76 + add(KeyEvent.KEYCODE_AT, LwjglGlfwKeycode.GLFW_KEY_2); + + add(KeyEvent.KEYCODE_PLUS, LwjglGlfwKeycode.GLFW_KEY_KP_ADD); + + // Page keys + add(KeyEvent.KEYCODE_PAGE_UP, LwjglGlfwKeycode.GLFW_KEY_PAGE_UP); //92 + add(KeyEvent.KEYCODE_PAGE_DOWN, LwjglGlfwKeycode.GLFW_KEY_PAGE_DOWN); + + add(KeyEvent.KEYCODE_ESCAPE, LwjglGlfwKeycode.GLFW_KEY_ESCAPE); + + // Control keys + add(KeyEvent.KEYCODE_CTRL_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL); + add(KeyEvent.KEYCODE_CTRL_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_CONTROL); + + add(KeyEvent.KEYCODE_CAPS_LOCK, LwjglGlfwKeycode.GLFW_KEY_CAPS_LOCK); + add(KeyEvent.KEYCODE_BREAK, LwjglGlfwKeycode.GLFW_KEY_PAUSE); + add(KeyEvent.KEYCODE_MOVE_HOME, LwjglGlfwKeycode.GLFW_KEY_HOME); + add(KeyEvent.KEYCODE_MOVE_END, LwjglGlfwKeycode.GLFW_KEY_END); + add(KeyEvent.KEYCODE_INSERT, LwjglGlfwKeycode.GLFW_KEY_INSERT); + + + // Fn keys + add(KeyEvent.KEYCODE_F1, LwjglGlfwKeycode.GLFW_KEY_F1); //131 + add(KeyEvent.KEYCODE_F2, LwjglGlfwKeycode.GLFW_KEY_F2); + add(KeyEvent.KEYCODE_F3, LwjglGlfwKeycode.GLFW_KEY_F3); + add(KeyEvent.KEYCODE_F4, LwjglGlfwKeycode.GLFW_KEY_F4); + add(KeyEvent.KEYCODE_F5, LwjglGlfwKeycode.GLFW_KEY_F5); + add(KeyEvent.KEYCODE_F6, LwjglGlfwKeycode.GLFW_KEY_F6); + add(KeyEvent.KEYCODE_F7, LwjglGlfwKeycode.GLFW_KEY_F7); + add(KeyEvent.KEYCODE_F8, LwjglGlfwKeycode.GLFW_KEY_F8); + add(KeyEvent.KEYCODE_F9, LwjglGlfwKeycode.GLFW_KEY_F9); + add(KeyEvent.KEYCODE_F10, LwjglGlfwKeycode.GLFW_KEY_F10); + add(KeyEvent.KEYCODE_F11, LwjglGlfwKeycode.GLFW_KEY_F11); + add(KeyEvent.KEYCODE_F12, LwjglGlfwKeycode.GLFW_KEY_F12); //142 + + // Num keys + add(KeyEvent.KEYCODE_NUM_LOCK, LwjglGlfwKeycode.GLFW_KEY_NUM_LOCK); //143 + add(KeyEvent.KEYCODE_NUMPAD_0, LwjglGlfwKeycode.GLFW_KEY_KP_0); + add(KeyEvent.KEYCODE_NUMPAD_1, LwjglGlfwKeycode.GLFW_KEY_KP_1); + add(KeyEvent.KEYCODE_NUMPAD_2, LwjglGlfwKeycode.GLFW_KEY_KP_2); + add(KeyEvent.KEYCODE_NUMPAD_3, LwjglGlfwKeycode.GLFW_KEY_KP_3); + add(KeyEvent.KEYCODE_NUMPAD_4, LwjglGlfwKeycode.GLFW_KEY_KP_4); + add(KeyEvent.KEYCODE_NUMPAD_5, LwjglGlfwKeycode.GLFW_KEY_KP_5); + add(KeyEvent.KEYCODE_NUMPAD_6, LwjglGlfwKeycode.GLFW_KEY_KP_6); + add(KeyEvent.KEYCODE_NUMPAD_7, LwjglGlfwKeycode.GLFW_KEY_KP_7); + add(KeyEvent.KEYCODE_NUMPAD_8, LwjglGlfwKeycode.GLFW_KEY_KP_8); + add(KeyEvent.KEYCODE_NUMPAD_9, LwjglGlfwKeycode.GLFW_KEY_KP_9); + add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LwjglGlfwKeycode.GLFW_KEY_KP_DIVIDE); + add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LwjglGlfwKeycode.GLFW_KEY_KP_MULTIPLY); + add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LwjglGlfwKeycode.GLFW_KEY_KP_SUBTRACT); + add(KeyEvent.KEYCODE_NUMPAD_ADD, LwjglGlfwKeycode.GLFW_KEY_KP_ADD); + add(KeyEvent.KEYCODE_NUMPAD_DOT, LwjglGlfwKeycode.GLFW_KEY_KP_DECIMAL); + add(KeyEvent.KEYCODE_NUMPAD_COMMA, LwjglGlfwKeycode.GLFW_KEY_COMMA); + add(KeyEvent.KEYCODE_NUMPAD_ENTER, LwjglGlfwKeycode.GLFW_KEY_KP_ENTER); + add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LwjglGlfwKeycode.GLFW_KEY_EQUAL); //161 + + + } + + public static boolean containsIndex(int index){ + return index >= 0; + } + + public static String[] generateKeyName() { + if (androidKeyNameArray == null) { + androidKeyNameArray = new String[sAndroidKeycodes.length]; + for(int i=0; i < androidKeyNameArray.length; ++i){ + androidKeyNameArray[i] = KeyEvent.keyCodeToString(sAndroidKeycodes[i]).replace("KEYCODE_", ""); + } + } + return androidKeyNameArray; + } + + public static void execKey(KeyEvent keyEvent, int valueIndex) { + //valueIndex points to where the value is stored in the array. + CallbackBridge.holdingAlt = keyEvent.isAltPressed(); + CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn(); + CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed(); + CallbackBridge.holdingNumlock = keyEvent.isNumLockOn(); + CallbackBridge.holdingShift = keyEvent.isShiftPressed(); + + System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel()); + char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000'); + sendKeyPress( + getValueByIndex(valueIndex), + key, + 0, + CallbackBridge.getCurrentMods(), + keyEvent.getAction() == KeyEvent.ACTION_DOWN); + } + + public static void execKeyIndex(int index){ + //Send a quick key press. + sendKeyPress(getValueByIndex(index)); + } + + public static short getValueByIndex(int index) { + return sLwjglKeycodes[index]; + } + + public static int getIndexByKey(int key){ + return Arrays.binarySearch(sAndroidKeycodes, key); + } + + /** @return the index at which the key is in the array, searching linearly */ + public static int getIndexByValue(int lwjglKey) { + //You should avoid using this function on performance critical areas + for (int i = 0; i < sLwjglKeycodes.length; i++) { + if(sLwjglKeycodes[i] == lwjglKey) return i; + } + return 0; + } + + private static void add(int androidKeycode, short LWJGLKeycode){ + sAndroidKeycodes[mTmpCount] = androidKeycode; + sLwjglKeycodes[mTmpCount] = LWJGLKeycode; + mTmpCount ++; + } +} \ No newline at end of file diff --git a/src/main/java/pojlib/input/GrabListener.java b/src/main/java/pojlib/input/GrabListener.java new file mode 100644 index 00000000..88e75ffc --- /dev/null +++ b/src/main/java/pojlib/input/GrabListener.java @@ -0,0 +1,5 @@ +package pojlib.input; + +public interface GrabListener { + void onGrabState(boolean isGrabbing); +} \ No newline at end of file diff --git a/src/main/java/pojlib/input/LwjglGlfwKeycode.java b/src/main/java/pojlib/input/LwjglGlfwKeycode.java new file mode 100644 index 00000000..3c3e2059 --- /dev/null +++ b/src/main/java/pojlib/input/LwjglGlfwKeycode.java @@ -0,0 +1,202 @@ +// Keycodes from https://github.com/glfw/glfw/blob/master/include/GLFW/glfw3.h + +/*-************************************************************************ + * GLFW 3.4 - www.glfw.org + * A library for OpenGL, window and input + *------------------------------------------------------------------------ + * Copyright (c) 2002-2006 Marcus Geelnard + * Copyright (c) 2006-2019 Camilla Löwy + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would + * be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + *************************************************************************/ + +package pojlib.input; + +@SuppressWarnings("unused") +public class LwjglGlfwKeycode { + /** The unknown key. */ + public static final short GLFW_KEY_UNKNOWN = 0; // should be -1 + + /** Printable keys. */ + public static final short + GLFW_KEY_SPACE = 32, + GLFW_KEY_APOSTROPHE = 39, + GLFW_KEY_COMMA = 44, + GLFW_KEY_MINUS = 45, + GLFW_KEY_PERIOD = 46, + GLFW_KEY_SLASH = 47, + GLFW_KEY_0 = 48, + GLFW_KEY_1 = 49, + GLFW_KEY_2 = 50, + GLFW_KEY_3 = 51, + GLFW_KEY_4 = 52, + GLFW_KEY_5 = 53, + GLFW_KEY_6 = 54, + GLFW_KEY_7 = 55, + GLFW_KEY_8 = 56, + GLFW_KEY_9 = 57, + GLFW_KEY_SEMICOLON = 59, + GLFW_KEY_EQUAL = 61, + GLFW_KEY_A = 65, + GLFW_KEY_B = 66, + GLFW_KEY_C = 67, + GLFW_KEY_D = 68, + GLFW_KEY_E = 69, + GLFW_KEY_F = 70, + GLFW_KEY_G = 71, + GLFW_KEY_H = 72, + GLFW_KEY_I = 73, + GLFW_KEY_J = 74, + GLFW_KEY_K = 75, + GLFW_KEY_L = 76, + GLFW_KEY_M = 77, + GLFW_KEY_N = 78, + GLFW_KEY_O = 79, + GLFW_KEY_P = 80, + GLFW_KEY_Q = 81, + GLFW_KEY_R = 82, + GLFW_KEY_S = 83, + GLFW_KEY_T = 84, + GLFW_KEY_U = 85, + GLFW_KEY_V = 86, + GLFW_KEY_W = 87, + GLFW_KEY_X = 88, + GLFW_KEY_Y = 89, + GLFW_KEY_Z = 90, + GLFW_KEY_LEFT_BRACKET = 91, + GLFW_KEY_BACKSLASH = 92, + GLFW_KEY_RIGHT_BRACKET = 93, + GLFW_KEY_GRAVE_ACCENT = 96, + GLFW_KEY_WORLD_1 = 161, + GLFW_KEY_WORLD_2 = 162; + + /** Function keys. */ + public static final short + GLFW_KEY_ESCAPE = 256, + GLFW_KEY_ENTER = 257, + GLFW_KEY_TAB = 258, + GLFW_KEY_BACKSPACE = 259, + GLFW_KEY_INSERT = 260, + GLFW_KEY_DELETE = 261, + GLFW_KEY_RIGHT = 262, + GLFW_KEY_LEFT = 263, + GLFW_KEY_DOWN = 264, + GLFW_KEY_UP = 265, + GLFW_KEY_PAGE_UP = 266, + GLFW_KEY_PAGE_DOWN = 267, + GLFW_KEY_HOME = 268, + GLFW_KEY_END = 269, + GLFW_KEY_CAPS_LOCK = 280, + GLFW_KEY_SCROLL_LOCK = 281, + GLFW_KEY_NUM_LOCK = 282, + GLFW_KEY_PRINT_SCREEN = 283, + GLFW_KEY_PAUSE = 284, + GLFW_KEY_F1 = 290, + GLFW_KEY_F2 = 291, + GLFW_KEY_F3 = 292, + GLFW_KEY_F4 = 293, + GLFW_KEY_F5 = 294, + GLFW_KEY_F6 = 295, + GLFW_KEY_F7 = 296, + GLFW_KEY_F8 = 297, + GLFW_KEY_F9 = 298, + GLFW_KEY_F10 = 299, + GLFW_KEY_F11 = 300, + GLFW_KEY_F12 = 301, + GLFW_KEY_F13 = 302, + GLFW_KEY_F14 = 303, + GLFW_KEY_F15 = 304, + GLFW_KEY_F16 = 305, + GLFW_KEY_F17 = 306, + GLFW_KEY_F18 = 307, + GLFW_KEY_F19 = 308, + GLFW_KEY_F20 = 309, + GLFW_KEY_F21 = 310, + GLFW_KEY_F22 = 311, + GLFW_KEY_F23 = 312, + GLFW_KEY_F24 = 313, + GLFW_KEY_F25 = 314, + GLFW_KEY_KP_0 = 320, + GLFW_KEY_KP_1 = 321, + GLFW_KEY_KP_2 = 322, + GLFW_KEY_KP_3 = 323, + GLFW_KEY_KP_4 = 324, + GLFW_KEY_KP_5 = 325, + GLFW_KEY_KP_6 = 326, + GLFW_KEY_KP_7 = 327, + GLFW_KEY_KP_8 = 328, + GLFW_KEY_KP_9 = 329, + GLFW_KEY_KP_DECIMAL = 330, + GLFW_KEY_KP_DIVIDE = 331, + GLFW_KEY_KP_MULTIPLY = 332, + GLFW_KEY_KP_SUBTRACT = 333, + GLFW_KEY_KP_ADD = 334, + GLFW_KEY_KP_ENTER = 335, + GLFW_KEY_KP_EQUAL = 336, + GLFW_KEY_LEFT_SHIFT = 340, + GLFW_KEY_LEFT_CONTROL = 341, + GLFW_KEY_LEFT_ALT = 342, + GLFW_KEY_LEFT_SUPER = 343, + GLFW_KEY_RIGHT_SHIFT = 344, + GLFW_KEY_RIGHT_CONTROL = 345, + GLFW_KEY_RIGHT_ALT = 346, + GLFW_KEY_RIGHT_SUPER = 347, + GLFW_KEY_MENU = 348, + GLFW_KEY_LAST = GLFW_KEY_MENU; + + /** If this bit is set one or more Shift keys were held down. */ + public static final int GLFW_MOD_SHIFT = 0x1; + + /** If this bit is set one or more Control keys were held down. */ + public static final int GLFW_MOD_CONTROL = 0x2; + + /** If this bit is set one or more Alt keys were held down. */ + public static final int GLFW_MOD_ALT = 0x4; + + /** If this bit is set one or more Super keys were held down. */ + public static final int GLFW_MOD_SUPER = 0x8; + + /** If this bit is set the Caps Lock key is enabled and the LOCK_KEY_MODS input mode is set. */ + public static final int GLFW_MOD_CAPS_LOCK = 0x10; + + /** If this bit is set the Num Lock key is enabled and the LOCK_KEY_MODS input mode is set. */ + public static final int GLFW_MOD_NUM_LOCK = 0x20; + + + /** Mouse buttons. See mouse button input for how these are used. */ + public static final short + GLFW_MOUSE_BUTTON_1 = 0, + GLFW_MOUSE_BUTTON_2 = 1, + GLFW_MOUSE_BUTTON_3 = 2, + GLFW_MOUSE_BUTTON_4 = 3, + GLFW_MOUSE_BUTTON_5 = 4, + GLFW_MOUSE_BUTTON_6 = 5, + GLFW_MOUSE_BUTTON_7 = 6, + GLFW_MOUSE_BUTTON_8 = 7, + GLFW_MOUSE_BUTTON_LAST = GLFW_MOUSE_BUTTON_8, + GLFW_MOUSE_BUTTON_LEFT = GLFW_MOUSE_BUTTON_1, + GLFW_MOUSE_BUTTON_RIGHT = GLFW_MOUSE_BUTTON_2, + GLFW_MOUSE_BUTTON_MIDDLE = GLFW_MOUSE_BUTTON_3; + + public static final int + GLFW_VISIBLE = 0x20004, + GLFW_HOVERED = 0x2000B; +} \ No newline at end of file diff --git a/src/main/java/pojlib/input/gamepad/DefaultDataProvider.java b/src/main/java/pojlib/input/gamepad/DefaultDataProvider.java new file mode 100644 index 00000000..62c18d89 --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/DefaultDataProvider.java @@ -0,0 +1,33 @@ +package pojlib.input.gamepad; + +import pojlib.input.GrabListener; + +import org.lwjgl.glfw.CallbackBridge; + +public class DefaultDataProvider implements GamepadDataProvider { + public static final DefaultDataProvider INSTANCE = new DefaultDataProvider(); + + // Cannot instantiate this class publicly + private DefaultDataProvider() {} + + @Override + public GamepadMap getGameMap() { + return GamepadMapStore.getGameMap(); + } + + + @Override + public GamepadMap getMenuMap() { + return GamepadMapStore.getMenuMap(); + } + + @Override + public boolean isGrabbing() { + return CallbackBridge.isGrabbing(); + } + + @Override + public void attachGrabListener(GrabListener grabListener) { + CallbackBridge.addGrabListener(grabListener); + } +} diff --git a/src/main/java/pojlib/input/gamepad/Gamepad.java b/src/main/java/pojlib/input/gamepad/Gamepad.java new file mode 100644 index 00000000..b390f3f2 --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/Gamepad.java @@ -0,0 +1,418 @@ +package pojlib.input.gamepad; + +import static android.view.MotionEvent.AXIS_HAT_X; +import static android.view.MotionEvent.AXIS_HAT_Y; +import static android.view.MotionEvent.AXIS_LTRIGGER; +import static android.view.MotionEvent.AXIS_RTRIGGER; +import static android.view.MotionEvent.AXIS_RZ; +import static android.view.MotionEvent.AXIS_X; +import static android.view.MotionEvent.AXIS_Y; +import static android.view.MotionEvent.AXIS_Z; + +import android.content.Context; +import android.view.Choreographer; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import org.lwjgl.glfw.CallbackBridge; + +import static org.lwjgl.glfw.CallbackBridge.sendKeyPress; +import static org.lwjgl.glfw.CallbackBridge.sendMouseButton; + +import static pojlib.UnityPlayerActivity.currentDisplayMetrics; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_EAST; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_NONE; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_NORTH; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_NORTH_EAST; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_NORTH_WEST; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_SOUTH; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_SOUTH_EAST; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_SOUTH_WEST; +import static pojlib.input.gamepad.GamepadJoystick.DIRECTION_WEST; +import static pojlib.input.gamepad.GamepadJoystick.isJoystickEvent; +import static pojlib.util.MCOptionUtils.getMcScale; + +import androidx.core.math.MathUtils; + +import fr.spse.gamepad_remapper.GamepadHandler; +import fr.spse.gamepad_remapper.Settings; +import pojlib.input.GrabListener; +import pojlib.input.LwjglGlfwKeycode; +import pojlib.util.MCOptionUtils; + +public class Gamepad implements GrabListener, GamepadHandler { + + /* Resolution scaler option, allow downsizing a window */ + private final float mScaleFactor = 1; // LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f; + + /* Sensitivity, adjusted according to screen size */ + private final double mSensitivityFactor = (1.4 * (1080f/ currentDisplayMetrics.heightPixels)); + private final GamepadJoystick mLeftJoystick; + private int mCurrentJoystickDirection = DIRECTION_NONE; + + private final GamepadJoystick mRightJoystick; + private float mLastHorizontalValue = 0.0f; + private float mLastVerticalValue = 0.0f; + + private static final double MOUSE_MAX_ACCELERATION = 2f; + + private double mMouseMagnitude; + private double mMouseAngle; + private double mMouseSensitivity = 19; + + private GamepadMap mGameMap; + private GamepadMap mMenuMap; + private GamepadMap mCurrentMap; + + private boolean isGrabbing; + + + /* Choreographer with time to compute delta on ticking */ + private final Choreographer mScreenChoreographer; + private long mLastFrameTime; + + /* Listen for change in gui scale */ + @SuppressWarnings("FieldCanBeLocal") //the field is used in a WeakReference + private final MCOptionUtils.MCOptionListener mGuiScaleListener = () -> notifyGUISizeChange(getMcScale()); + + private final GamepadDataProvider mMapProvider; + + public Gamepad(InputDevice inputDevice, GamepadDataProvider mapProvider){ + Settings.setDeadzoneScale(1f); + + mScreenChoreographer = Choreographer.getInstance(); + Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + tick(frameTimeNanos); + mScreenChoreographer.postFrameCallback(this); + } + }; + mScreenChoreographer.postFrameCallback(frameCallback); + mLastFrameTime = System.nanoTime(); + + /* Add the listener for the cross hair */ + MCOptionUtils.addMCOptionListener(mGuiScaleListener); + + mLeftJoystick = new GamepadJoystick(AXIS_X, AXIS_Y, inputDevice); + mRightJoystick = new GamepadJoystick(AXIS_Z, AXIS_RZ, inputDevice); + + mMapProvider = mapProvider; + + CallbackBridge.sendCursorPos(CallbackBridge.windowWidth/2f, CallbackBridge.windowHeight/2f); + + reloadGamepadMaps(); + mMapProvider.attachGrabListener(this); + } + + + public void reloadGamepadMaps() { + if(mGameMap != null) mGameMap.resetPressedState(); + if(mMenuMap != null) mMenuMap.resetPressedState(); + GamepadMapStore.load(); + mGameMap = mMapProvider.getGameMap(); + mMenuMap = mMapProvider.getMenuMap(); + mCurrentMap = mGameMap; + // Force state refresh + boolean currentGrab = CallbackBridge.isGrabbing(); + isGrabbing = !currentGrab; + onGrabState(currentGrab); + } + + public void updateJoysticks(){ + updateDirectionalJoystick(); + updateMouseJoystick(); + } + + public void notifyGUISizeChange(int newSize){ + //Change the pointer size to match UI + int size = (int) ((22 * newSize) / mScaleFactor); + } + + + public static void sendInput(short[] keycodes, boolean isDown){ + for(short keycode : keycodes){ + switch (keycode){ + case GamepadMap.MOUSE_SCROLL_DOWN: + if(isDown) CallbackBridge.sendScroll(0, -1); + break; + case GamepadMap.MOUSE_SCROLL_UP: + if(isDown) CallbackBridge.sendScroll(0, 1); + break; + case GamepadMap.MOUSE_LEFT: + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown); + break; + case GamepadMap.MOUSE_MIDDLE: + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown); + break; + case GamepadMap.MOUSE_RIGHT: + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown); + break; + case GamepadMap.UNSPECIFIED: + break; + + default: + sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown); + CallbackBridge.setModifiers(keycode, isDown); + break; + } + } + + } + + public static boolean isGamepadEvent(MotionEvent event){ + return isJoystickEvent(event); + } + + public static boolean isGamepadEvent(KeyEvent event){ + boolean isGamepad = ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + || ((event.getDevice() != null) && ((event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)); + + return isGamepad && GamepadDpad.isDpadEvent(event); + } + + /** + * Send the new mouse position, computing the delta + * @param frameTimeNanos The time to render the frame, used to compute mouse delta + */ + private void tick(long frameTimeNanos){ + //update mouse position + long newFrameTime = System.nanoTime(); + if(mLastHorizontalValue != 0 || mLastVerticalValue != 0){ + + double acceleration = Math.pow(mMouseMagnitude, MOUSE_MAX_ACCELERATION); + if(acceleration > 1) acceleration = 1; + + // Compute delta since last tick time + float deltaX = (float) (Math.cos(mMouseAngle) * acceleration * mMouseSensitivity); + float deltaY = (float) (Math.sin(mMouseAngle) * acceleration * mMouseSensitivity); + newFrameTime = System.nanoTime(); // More accurate delta + float deltaTimeScale = ((newFrameTime - mLastFrameTime) / 16666666f); // Scale of 1 = 60Hz + deltaX *= deltaTimeScale; + deltaY *= deltaTimeScale; + + CallbackBridge.mouseX += deltaX; + CallbackBridge.mouseY -= deltaY; + + if(!isGrabbing){ + CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth); + CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight); + } + + //Send the mouse to the game + CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); + } + + // Update last nano time + mLastFrameTime = newFrameTime; + } + + private void updateMouseJoystick(){ + GamepadJoystick currentJoystick = isGrabbing ? mRightJoystick : mLeftJoystick; + float horizontalValue = currentJoystick.getHorizontalAxis(); + float verticalValue = currentJoystick.getVerticalAxis(); + if(horizontalValue != mLastHorizontalValue || verticalValue != mLastVerticalValue){ + mLastHorizontalValue = horizontalValue; + mLastVerticalValue = verticalValue; + + mMouseMagnitude = currentJoystick.getMagnitude(); + mMouseAngle = currentJoystick.getAngleRadian(); + + tick(System.nanoTime()); + return; + } + mLastHorizontalValue = horizontalValue; + mLastVerticalValue = verticalValue; + + mMouseMagnitude = currentJoystick.getMagnitude(); + mMouseAngle = currentJoystick.getAngleRadian(); + + } + + private void updateDirectionalJoystick(){ + GamepadJoystick currentJoystick = isGrabbing ? mLeftJoystick : mRightJoystick; + + int lastJoystickDirection = mCurrentJoystickDirection; + mCurrentJoystickDirection = currentJoystick.getHeightDirection(); + + if(mCurrentJoystickDirection == lastJoystickDirection) return; + + sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap()); + sendDirectionalKeycode(mCurrentJoystickDirection, true, getCurrentMap()); + } + + + private GamepadMap getCurrentMap(){ + return mCurrentMap; + } + + private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){ + switch (direction){ + case DIRECTION_NORTH: + map.DIRECTION_FORWARD.update(isDown); + break; + case DIRECTION_NORTH_EAST: + map.DIRECTION_FORWARD.update(isDown); + map.DIRECTION_RIGHT.update(isDown); + break; + case DIRECTION_EAST: + map.DIRECTION_RIGHT.update(isDown); + break; + case DIRECTION_SOUTH_EAST: + map.DIRECTION_RIGHT.update(isDown); + map.DIRECTION_BACKWARD.update(isDown); + break; + case DIRECTION_SOUTH: + map.DIRECTION_BACKWARD.update(isDown); + break; + case DIRECTION_SOUTH_WEST: + map.DIRECTION_BACKWARD.update(isDown); + map.DIRECTION_LEFT.update(isDown); + break; + case DIRECTION_WEST: + map.DIRECTION_LEFT.update(isDown); + break; + case DIRECTION_NORTH_WEST: + map.DIRECTION_FORWARD.update(isDown); + map.DIRECTION_LEFT.update(isDown); + break; + } + } + + /** Update the grabbing state, and change the currentMap, mouse position and sensibility */ + @Override + public void onGrabState(boolean isGrabbing) { + boolean lastGrabbingValue = this.isGrabbing; + this.isGrabbing = isGrabbing; + if(lastGrabbingValue == isGrabbing) return; + + // Switch grabbing state then + mCurrentMap.resetPressedState(); + if(isGrabbing){ + mCurrentMap = mGameMap; + mMouseSensitivity = 18; + return; + } + + mCurrentMap = mMenuMap; + sendDirectionalKeycode(mCurrentJoystickDirection, false, mGameMap); // removing what we were doing + + CallbackBridge.sendCursorPos(CallbackBridge.windowWidth/2f, CallbackBridge.windowHeight/2f); + // Sensitivity in menu is MC and HARDWARE resolution dependent + mMouseSensitivity = 19 * mScaleFactor / mSensitivityFactor; + } + + @Override + public void handleGamepadInput(int keycode, float value) { + boolean isKeyEventDown = value == 1f; + switch (keycode){ + case KeyEvent.KEYCODE_BUTTON_A: + getCurrentMap().BUTTON_A.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_B: + getCurrentMap().BUTTON_B.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_X: + getCurrentMap().BUTTON_X.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_Y: + getCurrentMap().BUTTON_Y.update(isKeyEventDown); + break; + + //Shoulders + case KeyEvent.KEYCODE_BUTTON_L1: + getCurrentMap().SHOULDER_LEFT.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_R1: + getCurrentMap().SHOULDER_RIGHT.update(isKeyEventDown); + break; + + //Triggers + case KeyEvent.KEYCODE_BUTTON_L2: + getCurrentMap().TRIGGER_LEFT.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_R2: + getCurrentMap().TRIGGER_RIGHT.update(isKeyEventDown); + break; + + //L3 || R3 + case KeyEvent.KEYCODE_BUTTON_THUMBL: + getCurrentMap().THUMBSTICK_LEFT.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + getCurrentMap().THUMBSTICK_RIGHT.update(isKeyEventDown); + break; + + //DPAD + case KeyEvent.KEYCODE_DPAD_UP: + getCurrentMap().DPAD_UP.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + getCurrentMap().DPAD_DOWN.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + getCurrentMap().DPAD_LEFT.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + getCurrentMap().DPAD_RIGHT.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + getCurrentMap().DPAD_RIGHT.update(false); + getCurrentMap().DPAD_LEFT.update(false); + getCurrentMap().DPAD_UP.update(false); + getCurrentMap().DPAD_DOWN.update(false); + break; + + //Start/select + case KeyEvent.KEYCODE_BUTTON_START: + getCurrentMap().BUTTON_START.update(isKeyEventDown); + break; + case KeyEvent.KEYCODE_BUTTON_SELECT: + getCurrentMap().BUTTON_SELECT.update(isKeyEventDown); + break; + + /* Now, it is time for motionEvents */ + case AXIS_HAT_X: + getCurrentMap().DPAD_RIGHT.update(value > 0.85); + getCurrentMap().DPAD_LEFT.update(value < -0.85); + break; + case AXIS_HAT_Y: + getCurrentMap().DPAD_DOWN.update(value > 0.85); + getCurrentMap().DPAD_UP.update(value < -0.85); + break; + + // Left joystick + case AXIS_X: + mLeftJoystick.setXAxisValue(value); + updateJoysticks(); + break; + case AXIS_Y: + mLeftJoystick.setYAxisValue(value); + updateJoysticks(); + break; + + // Right joystick + case AXIS_Z: + mRightJoystick.setXAxisValue(value); + updateJoysticks(); + break; + case AXIS_RZ: + mRightJoystick.setYAxisValue(value); + updateJoysticks(); + break; + + // Triggers + case AXIS_RTRIGGER: + getCurrentMap().TRIGGER_RIGHT.update(value > 0.5); + break; + case AXIS_LTRIGGER: + getCurrentMap().TRIGGER_LEFT.update(value > 0.5); + break; + + default: + sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), isKeyEventDown); + break; + } + } +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadButton.java b/src/main/java/pojlib/input/gamepad/GamepadButton.java new file mode 100644 index 00000000..f479affd --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadButton.java @@ -0,0 +1,30 @@ +package pojlib.input.gamepad; + +/** + * This class corresponds to a button that does exist on the gamepad + */ +public class GamepadButton extends GamepadEmulatedButton { + public boolean isToggleable = false; + private boolean mIsToggled = false; + + + @Override + protected void onDownStateChanged(boolean isDown) { + if(isToggleable && isDown){ + mIsToggled = !mIsToggled; + Gamepad.sendInput(keycodes, mIsToggled); + return; + } + super.onDownStateChanged(isDown); + } + + @Override + public void resetButtonState() { + if(!mIsDown && mIsToggled) { + Gamepad.sendInput(keycodes, false); + mIsToggled = false; + } else { + super.resetButtonState(); + } + } +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadDataProvider.java b/src/main/java/pojlib/input/gamepad/GamepadDataProvider.java new file mode 100644 index 00000000..ecb096b0 --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadDataProvider.java @@ -0,0 +1,10 @@ +package pojlib.input.gamepad; + +import pojlib.input.GrabListener; + +public interface GamepadDataProvider { + GamepadMap getMenuMap(); + GamepadMap getGameMap(); + boolean isGrabbing(); + void attachGrabListener(GrabListener grabListener); +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadDpad.java b/src/main/java/pojlib/input/gamepad/GamepadDpad.java new file mode 100644 index 00000000..d1b86094 --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadDpad.java @@ -0,0 +1,62 @@ +package pojlib.input.gamepad; + +import static android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC; +import static android.view.InputDevice.SOURCE_GAMEPAD; +import static android.view.KeyEvent.KEYCODE_DPAD_CENTER; +import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; +import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; +import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; +import static android.view.KeyEvent.KEYCODE_DPAD_UP; + +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + + +public class GamepadDpad { + private int mLastKeycode = KEYCODE_DPAD_CENTER; + + /** + * Convert the event to a 2 int array: keycode and keyAction, similar to a keyEvent + * @param event The motion to convert + * @return int[0] keycode, int[1] keyAction + */ + public int[] convertEvent(MotionEvent event){ + // Use the hat axis value to find the D-pad direction + float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X); + float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y); + int action = KeyEvent.ACTION_DOWN; + + // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad + // LEFT and RIGHT direction accordingly. + if (Float.compare(xaxis, -1.0f) == 0) { + mLastKeycode = KEYCODE_DPAD_LEFT; + } else if (Float.compare(xaxis, 1.0f) == 0) { + mLastKeycode = KEYCODE_DPAD_RIGHT; + } + // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad + // UP and DOWN direction accordingly. + else if (Float.compare(yaxis, -1.0f) == 0) { + mLastKeycode = KEYCODE_DPAD_UP; + } else if (Float.compare(yaxis, 1.0f) == 0) { + mLastKeycode = KEYCODE_DPAD_DOWN; + }else { + //No keycode change + action = KeyEvent.ACTION_UP; + } + + return new int[]{mLastKeycode, action}; + + } + + @SuppressWarnings("unused") public static boolean isDpadEvent(MotionEvent event) { + // Check that input comes from a device with directional pads. + // And... also the joystick since it declares sometimes as a joystick. + return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK; + } + + public static boolean isDpadEvent(KeyEvent event){ + //return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC); + return event.isFromSource(SOURCE_GAMEPAD) && event.getDevice().getKeyboardType() != KEYBOARD_TYPE_ALPHABETIC; + } +} \ No newline at end of file diff --git a/src/main/java/pojlib/input/gamepad/GamepadEmulatedButton.java b/src/main/java/pojlib/input/gamepad/GamepadEmulatedButton.java new file mode 100644 index 00000000..65376031 --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadEmulatedButton.java @@ -0,0 +1,33 @@ +package pojlib.input.gamepad; + +import android.view.KeyEvent; + +/** + * This class corresponds to a button that does not physically exist on the gamepad, but is + * emulated from other inputs on it (like WASD directional keys) + */ +public class GamepadEmulatedButton { + public short[] keycodes; + protected boolean mIsDown = false; + + public void update(KeyEvent event) { + boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN); + update(isKeyDown); + } + + public void update(boolean isKeyDown){ + if(isKeyDown != mIsDown){ + mIsDown = isKeyDown; + onDownStateChanged(mIsDown); + } + } + + public void resetButtonState() { + if(mIsDown) Gamepad.sendInput(keycodes, false); + mIsDown = false; + } + + protected void onDownStateChanged(boolean isDown) { + Gamepad.sendInput(keycodes, mIsDown); + } +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadJoystick.java b/src/main/java/pojlib/input/gamepad/GamepadJoystick.java new file mode 100644 index 00000000..6826480a --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadJoystick.java @@ -0,0 +1,85 @@ +package pojlib.input.gamepad; + +import android.view.InputDevice; +import android.view.MotionEvent; + +import pojlib.util.MathUtils; + +public class GamepadJoystick { + + //Directions + public static final int DIRECTION_NONE = -1; //GamepadJoystick at the center + + public static final int DIRECTION_EAST = 0; + public static final int DIRECTION_NORTH_EAST = 1; + public static final int DIRECTION_NORTH = 2; + public static final int DIRECTION_NORTH_WEST = 3; + public static final int DIRECTION_WEST = 4; + public static final int DIRECTION_SOUTH_WEST = 5; + public static final int DIRECTION_SOUTH = 6; + public static final int DIRECTION_SOUTH_EAST = 7; + + private final InputDevice mInputDevice; + + private final int mHorizontalAxis; + private final int mVerticalAxis; + private float mVerticalAxisValue = 0; + private float mHorizontalAxisValue = 0; + + public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){ + mHorizontalAxis = horizontalAxis; + mVerticalAxis = verticalAxis; + this.mInputDevice = device; + } + + public double getAngleRadian(){ + //From -PI to PI + // TODO misuse of the deadzone here ! + return -Math.atan2(getVerticalAxis(), getHorizontalAxis()); + } + + + public double getAngleDegree(){ + //From 0 to 360 degrees + double result = Math.toDegrees(getAngleRadian()); + if(result < 0) result += 360; + + return result; + } + + public double getMagnitude(){ + float x = Math.abs(mHorizontalAxisValue); + float y = Math.abs(mVerticalAxisValue); + + return MathUtils.dist(0,0, x, y); + } + + public float getVerticalAxis(){ + return mVerticalAxisValue; + } + + public float getHorizontalAxis(){ + return mHorizontalAxisValue; + } + + public static boolean isJoystickEvent(MotionEvent event){ + return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK + && event.getAction() == MotionEvent.ACTION_MOVE; + } + + + public int getHeightDirection(){ + if(getMagnitude() == 0) return DIRECTION_NONE; + return ((int) ((getAngleDegree()+22.5)/45)) % 8; + } + + + /* Setters */ + public void setXAxisValue(float value){ + this.mHorizontalAxisValue = value; + } + + public void setYAxisValue(float value){ + this.mVerticalAxisValue = value; + } +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadMap.java b/src/main/java/pojlib/input/gamepad/GamepadMap.java new file mode 100644 index 00000000..27c2a1cd --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadMap.java @@ -0,0 +1,191 @@ +package pojlib.input.gamepad; + +import pojlib.input.LwjglGlfwKeycode; + +public class GamepadMap { + + + public static final short MOUSE_SCROLL_DOWN = -1; + public static final short MOUSE_SCROLL_UP = -2; + // Made mouse keycodes their own specials because managing special keycodes above 0 + // proved to be complicated + public static final short MOUSE_LEFT = -3; + public static final short MOUSE_MIDDLE = -4; + public static final short MOUSE_RIGHT = -5; + // Workaround, because GLFW_KEY_UNKNOWN and GLFW_MOUSE_BUTTON_LEFT are both 0 + public static final short UNSPECIFIED = -6; + + /* + This class is just here to store the mapping + can be modified to create re-mappable controls I guess + + Be warned, you should define ALL keys if you want to avoid a non defined exception + */ + + public GamepadButton BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, BUTTON_START, BUTTON_SELECT, + TRIGGER_RIGHT, TRIGGER_LEFT, SHOULDER_RIGHT, SHOULDER_LEFT, THUMBSTICK_RIGHT, + THUMBSTICK_LEFT, DPAD_UP, DPAD_DOWN, DPAD_RIGHT, DPAD_LEFT; + + public GamepadEmulatedButton DIRECTION_FORWARD, DIRECTION_BACKWARD, DIRECTION_RIGHT, DIRECTION_LEFT; + + /* + * Sets all buttons to a not pressed state, sending an input if needed + */ + public void resetPressedState(){ + BUTTON_A.resetButtonState(); + BUTTON_B.resetButtonState(); + BUTTON_X.resetButtonState(); + BUTTON_Y.resetButtonState(); + + BUTTON_START.resetButtonState(); + BUTTON_SELECT.resetButtonState(); + + TRIGGER_LEFT.resetButtonState(); + TRIGGER_RIGHT.resetButtonState(); + + SHOULDER_LEFT.resetButtonState(); + SHOULDER_RIGHT.resetButtonState(); + + THUMBSTICK_LEFT.resetButtonState(); + THUMBSTICK_RIGHT.resetButtonState(); + + DPAD_UP.resetButtonState(); + DPAD_RIGHT.resetButtonState(); + DPAD_DOWN.resetButtonState(); + DPAD_LEFT.resetButtonState(); + + } + + private static GamepadMap createAndInitializeButtons() { + GamepadMap gamepadMap = new GamepadMap(); + gamepadMap.BUTTON_A = new GamepadButton(); + gamepadMap.BUTTON_B = new GamepadButton(); + gamepadMap.BUTTON_X = new GamepadButton(); + gamepadMap.BUTTON_Y = new GamepadButton(); + + gamepadMap.BUTTON_START = new GamepadButton(); + gamepadMap.BUTTON_SELECT = new GamepadButton(); + + gamepadMap.TRIGGER_RIGHT = new GamepadButton(); + gamepadMap.TRIGGER_LEFT = new GamepadButton(); + + gamepadMap.SHOULDER_RIGHT = new GamepadButton(); + gamepadMap.SHOULDER_LEFT = new GamepadButton(); + + gamepadMap.DIRECTION_FORWARD = new GamepadEmulatedButton(); + gamepadMap.DIRECTION_BACKWARD = new GamepadEmulatedButton(); + gamepadMap.DIRECTION_RIGHT = new GamepadEmulatedButton(); + gamepadMap.DIRECTION_LEFT = new GamepadEmulatedButton(); + + gamepadMap.THUMBSTICK_RIGHT = new GamepadButton(); + gamepadMap.THUMBSTICK_LEFT = new GamepadButton(); + + gamepadMap.DPAD_UP = new GamepadButton(); + gamepadMap.DPAD_RIGHT = new GamepadButton(); + gamepadMap.DPAD_DOWN = new GamepadButton(); + gamepadMap.DPAD_LEFT = new GamepadButton(); + return gamepadMap; + } + + /* + * Returns a pre-done mapping used when the mouse is grabbed by the game. + */ + public static GamepadMap getDefaultGameMap(){ + GamepadMap gameMap = GamepadMap.createEmptyMap(); + + gameMap.BUTTON_A.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_SPACE; + gameMap.BUTTON_B.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_Q; + gameMap.BUTTON_X.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_E; + gameMap.BUTTON_Y.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_F; + + gameMap.DIRECTION_FORWARD.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_W; + gameMap.DIRECTION_BACKWARD.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_S; + gameMap.DIRECTION_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_D; + gameMap.DIRECTION_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_A; + + gameMap.DPAD_UP.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; + gameMap.DPAD_DOWN.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_O; //For mods ? + gameMap.DPAD_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_K; //For mods ? + gameMap.DPAD_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_J; //For mods ? + + gameMap.SHOULDER_LEFT.keycodes[0] = GamepadMap.MOUSE_SCROLL_UP; + gameMap.SHOULDER_RIGHT.keycodes[0] = GamepadMap.MOUSE_SCROLL_DOWN; + + gameMap.TRIGGER_LEFT.keycodes[0] = GamepadMap.MOUSE_RIGHT; + gameMap.TRIGGER_RIGHT.keycodes[0] = GamepadMap.MOUSE_LEFT; + + gameMap.THUMBSTICK_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL; + gameMap.THUMBSTICK_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; + gameMap.THUMBSTICK_RIGHT.isToggleable = true; + + gameMap.BUTTON_START.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; + gameMap.BUTTON_SELECT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_TAB; + + return gameMap; + } + + /* + * Returns a pre-done mapping used when the mouse is NOT grabbed by the game. + */ + public static GamepadMap getDefaultMenuMap(){ + GamepadMap menuMap = GamepadMap.createEmptyMap(); + + menuMap.BUTTON_A.keycodes[0] = GamepadMap.MOUSE_LEFT; + menuMap.BUTTON_B.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; + menuMap.BUTTON_X.keycodes[0] = GamepadMap.MOUSE_RIGHT; + { + short[] keycodes = menuMap.BUTTON_Y.keycodes; + keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; + keycodes[1] = GamepadMap.MOUSE_RIGHT; + } + + { + short[] keycodes = menuMap.DIRECTION_FORWARD.keycodes; + keycodes[0] = keycodes[1] = keycodes[2] = keycodes[3] = GamepadMap.MOUSE_SCROLL_UP; + } + { + short[] keycodes = menuMap.DIRECTION_BACKWARD.keycodes; + keycodes[0] = keycodes[1] = keycodes[2] = keycodes[3] = GamepadMap.MOUSE_SCROLL_DOWN; + } + + menuMap.DPAD_UP.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_TAB; // QC Funnies + menuMap.DPAD_DOWN.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ENTER; //For mods ? + menuMap.DPAD_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_K; //For mods ? + menuMap.DPAD_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_J; //For mods ? + + menuMap.SHOULDER_LEFT.keycodes[0] = GamepadMap.MOUSE_SCROLL_UP; + menuMap.SHOULDER_RIGHT.keycodes[0] = GamepadMap.MOUSE_SCROLL_DOWN; + + menuMap.BUTTON_SELECT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; + + return menuMap; + } + + /* + * Returns all GamepadEmulatedButtons of the controller key map. + */ + public GamepadEmulatedButton[] getButtons(){ + return new GamepadEmulatedButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, + BUTTON_SELECT, BUTTON_START, + TRIGGER_LEFT, TRIGGER_RIGHT, + SHOULDER_LEFT, SHOULDER_RIGHT, + THUMBSTICK_LEFT, THUMBSTICK_RIGHT, + DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT, + DIRECTION_FORWARD, DIRECTION_BACKWARD, + DIRECTION_LEFT, DIRECTION_RIGHT}; + } + + /* + * Returns an pre-initialized GamepadMap with only empty keycodes + */ + @SuppressWarnings("unused") public static GamepadMap createEmptyMap(){ + GamepadMap emptyMap = createAndInitializeButtons(); + for(GamepadEmulatedButton button : emptyMap.getButtons()) + button.keycodes = new short[] {UNSPECIFIED, UNSPECIFIED, UNSPECIFIED, UNSPECIFIED}; + return emptyMap; + } + + public static String[] getSpecialKeycodeNames() { + return new String[] {"UNSPECIFIED", "MOUSE_RIGHT", "MOUSE_MIDDLE", "MOUSE_LEFT", "SCROLL_UP", "SCROLL_DOWN"}; + } +} diff --git a/src/main/java/pojlib/input/gamepad/GamepadMapStore.java b/src/main/java/pojlib/input/gamepad/GamepadMapStore.java new file mode 100644 index 00000000..fd61c10d --- /dev/null +++ b/src/main/java/pojlib/input/gamepad/GamepadMapStore.java @@ -0,0 +1,61 @@ +package pojlib.input.gamepad; + +import android.util.Log; + +import com.google.gson.JsonParseException; + +import java.io.File; +import java.io.IOException; + +import pojlib.util.Constants; +import pojlib.util.FileUtil; +import pojlib.util.GsonUtils; + +public class GamepadMapStore { + private static final File STORE_FILE = new File(Constants.USER_HOME, "gamepad_map.json"); + private static GamepadMapStore sMapStore; + private GamepadMap mInMenuMap; + private GamepadMap mInGameMap; + private static GamepadMapStore createDefault() { + GamepadMapStore mapStore = new GamepadMapStore(); + mapStore.mInGameMap = GamepadMap.getDefaultGameMap(); + mapStore.mInMenuMap = GamepadMap.getDefaultMenuMap(); + return mapStore; + } + + private static void loadIfNecessary() { + if(sMapStore == null) return; + load(); + } + + public static void load() { + GamepadMapStore mapStore = null; + if(STORE_FILE.exists() && STORE_FILE.canRead()) { + try { + String storeFileContent = FileUtil.read(STORE_FILE.getPath()); + mapStore = GsonUtils.GLOBAL_GSON.fromJson(storeFileContent, GamepadMapStore.class); + } catch (JsonParseException | IOException e) { + Log.w("GamepadMapStore", "Map store failed to load!", e); + } + } + if(mapStore == null) mapStore = createDefault(); + sMapStore = mapStore; + } + + public static void save() throws IOException { + if(sMapStore == null) throw new RuntimeException("Must load map store first!"); + FileUtil.ensureParentDirectory(STORE_FILE); + String jsonData = GsonUtils.GLOBAL_GSON.toJson(sMapStore); + FileUtil.write(STORE_FILE.getAbsolutePath(), jsonData.getBytes()); + } + + public static GamepadMap getGameMap() { + loadIfNecessary(); + return sMapStore.mInGameMap; + } + + public static GamepadMap getMenuMap() { + loadIfNecessary(); + return sMapStore.mInMenuMap; + } +} diff --git a/src/main/java/pojlib/util/FileUtil.java b/src/main/java/pojlib/util/FileUtil.java index 6e4e27c6..e149b77b 100644 --- a/src/main/java/pojlib/util/FileUtil.java +++ b/src/main/java/pojlib/util/FileUtil.java @@ -141,4 +141,29 @@ public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOExce return destFile; } + + /** + * @author PojavLauncherTeam + * Same as ensureDirectorySilently(), but throws an IOException telling why the check failed. + * @param targetFile the directory to check + * @throws IOException when the checks fail + */ + public static void ensureDirectory(File targetFile) throws IOException{ + if(targetFile.isFile()) throw new IOException("Target directory is a file"); + if(targetFile.exists()) { + if(!targetFile.canWrite()) throw new IOException("Target directory is not writable"); + }else if(!targetFile.mkdirs()) throw new IOException("Unable to create target directory"); + } + + /** + * @author PojavLauncherTeam + * Same as ensureParentDirectorySilently(), but throws an IOException telling why the check failed. + * @param targetFile the File whose parent should be checked + * @throws IOException when the checks fail + */ + public static void ensureParentDirectory(File targetFile) throws IOException{ + File parentFile = targetFile.getParentFile(); + if(parentFile == null) throw new IOException("targetFile does not have a parent"); + ensureDirectory(parentFile); + } } diff --git a/src/main/java/pojlib/util/JREUtils.java b/src/main/java/pojlib/util/JREUtils.java index 05d977c1..4424bd4f 100644 --- a/src/main/java/pojlib/util/JREUtils.java +++ b/src/main/java/pojlib/util/JREUtils.java @@ -190,8 +190,8 @@ public static int launchJavaVM(final Activity activity, final List JVMAr userArgs.add("-Xms" + 2048 + "M"); userArgs.add("-Xmx" + 3072 + "M"); } else { - userArgs.add("-Xms" + 2048 + "M"); - userArgs.add("-Xmx" + 2048 + "M"); + userArgs.add("-Xms" + 512 + "M"); + userArgs.add("-Xmx" + 4860 + "M"); } } diff --git a/src/main/java/pojlib/util/MCOptionUtils.java b/src/main/java/pojlib/util/MCOptionUtils.java new file mode 100644 index 00000000..44a3baaf --- /dev/null +++ b/src/main/java/pojlib/util/MCOptionUtils.java @@ -0,0 +1,142 @@ +package pojlib.util; + +import static org.lwjgl.glfw.CallbackBridge.windowHeight; +import static org.lwjgl.glfw.CallbackBridge.windowWidth; + +import android.os.Build; +import android.os.FileObserver; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; + +import pojlib.API; + +public class MCOptionUtils { + private static final HashMap sParameterMap = new HashMap<>(); + private static final ArrayList> sOptionListeners = new ArrayList<>(); + private static FileObserver sFileObserver; + private static String sOptionFolderPath = null; + public interface MCOptionListener { + /** Called when an option is changed. Don't know which one though */ + void onOptionChanged(); + } + + + public static void load(){ + load(sOptionFolderPath == null + ? API.currentInstance.gameDir + : sOptionFolderPath); + } + + public static void load(@NonNull String folderPath) { + File optionFile = new File(folderPath + "/options.txt"); + if(!optionFile.exists()) { + try { // Needed for new instances I guess :think: + optionFile.createNewFile(); + } catch (IOException e) { e.printStackTrace(); } + } + + if(sFileObserver == null || !Objects.equals(sOptionFolderPath, folderPath)){ + sOptionFolderPath = folderPath; + setupFileObserver(); + } + sOptionFolderPath = folderPath; // Yeah I know, it may be redundant + + sParameterMap.clear(); + + try { + BufferedReader reader = new BufferedReader(new FileReader(optionFile)); + String line; + while ((line = reader.readLine()) != null) { + int firstColonIndex = line.indexOf(':'); + if(firstColonIndex < 0) { + Log.w("QuestCraft", "No colon on line \""+line+"\", skipping"); + continue; + } + sParameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1)); + } + reader.close(); + } catch (IOException e) { + Log.w("QuestCraft", "Could not load options.txt", e); + } + } + + public static String get(String key) { + return sParameterMap.get(key); + } + + /** @return The stored Minecraft GUI scale, also auto-computed if on auto-mode or improper setting */ + public static int getMcScale() { + String str = MCOptionUtils.get("guiScale"); + int guiScale = (str == null ? 0 :Integer.parseInt(str)); + + int scale = Math.max(Math.min(windowWidth / 320, windowHeight / 240), 1); + if(scale < guiScale || guiScale == 0){ + guiScale = scale; + } + + return guiScale; + } + + /** Add a file observer to reload options on file change + * Listeners get notified of the change */ + private static void setupFileObserver(){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + sFileObserver = new FileObserver(new File(sOptionFolderPath + "/options.txt"), FileObserver.MODIFY) { + @Override + public void onEvent(int i, @Nullable String s) { + MCOptionUtils.load(); + notifyListeners(); + } + }; + }else{ + sFileObserver = new FileObserver(sOptionFolderPath + "/options.txt", FileObserver.MODIFY) { + @Override + public void onEvent(int i, @Nullable String s) { + MCOptionUtils.load(); + notifyListeners(); + } + }; + } + + sFileObserver.startWatching(); + } + + /** Notify the option listeners */ + public static void notifyListeners(){ + for(WeakReference weakReference : sOptionListeners){ + MCOptionListener optionListener = weakReference.get(); + if(optionListener == null) continue; + + optionListener.onOptionChanged(); + } + } + + /** Add an option listener, notice how we don't have a reference to it */ + public static void addMCOptionListener(MCOptionListener listener){ + sOptionListeners.add(new WeakReference<>(listener)); + } + + /** Remove a listener from existence, or at least, its reference here */ + public static void removeMCOptionListener(MCOptionListener listener){ + for(WeakReference weakReference : sOptionListeners){ + MCOptionListener optionListener = weakReference.get(); + if(optionListener == null) continue; + if(optionListener == listener){ + sOptionListeners.remove(weakReference); + return; + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/pojlib/util/MathUtils.java b/src/main/java/pojlib/util/MathUtils.java new file mode 100644 index 00000000..04e700ff --- /dev/null +++ b/src/main/java/pojlib/util/MathUtils.java @@ -0,0 +1,11 @@ +package pojlib.util; + +public class MathUtils { + + /** Returns the distance between two points. */ + public static float dist(float x1, float y1, float x2, float y2) { + final float x = (x2 - x1); + final float y = (y2 - y1); + return (float) Math.hypot(x, y); + } +} diff --git a/src/main/jni/Android.mk b/src/main/jni/Android.mk index 9bd606b4..b20234bf 100644 --- a/src/main/jni/Android.mk +++ b/src/main/jni/Android.mk @@ -50,6 +50,7 @@ LOCAL_SHARED_LIBRARIES := openvr_api LOCAL_SRC_FILES := \ egl_bridge.c \ utils.c \ + environ/environ.c \ input_bridge_v3.c include $(BUILD_SHARED_LIBRARY) @@ -59,6 +60,12 @@ LOCAL_SRC_FILES := \ stdio_is.c include $(BUILD_SHARED_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := pojavexec_awt +LOCAL_SRC_FILES := \ + awt_bridge.c +include $(BUILD_SHARED_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := jrelauncher LOCAL_SHARED_LIBRARIES := pojavexec diff --git a/src/main/jni/OpenOVR/OCOVR.a b/src/main/jni/OpenOVR/OCOVR.a index a1c4673e..c978f2a0 100644 Binary files a/src/main/jni/OpenOVR/OCOVR.a and b/src/main/jni/OpenOVR/OCOVR.a differ diff --git a/src/main/jni/OpenOVR/libDrvOpenXR.a b/src/main/jni/OpenOVR/libDrvOpenXR.a index 34099336..99f5a8a8 100644 Binary files a/src/main/jni/OpenOVR/libDrvOpenXR.a and b/src/main/jni/OpenOVR/libDrvOpenXR.a differ diff --git a/src/main/jni/OpenOVR/libOpenXR.a b/src/main/jni/OpenOVR/libOpenXR.a index c1b3e6b0..0e9e30d9 100644 Binary files a/src/main/jni/OpenOVR/libOpenXR.a and b/src/main/jni/OpenOVR/libOpenXR.a differ diff --git a/src/main/jni/awt_bridge.c b/src/main/jni/awt_bridge.c new file mode 100644 index 00000000..79bb3ca4 --- /dev/null +++ b/src/main/jni/awt_bridge.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include + +static JavaVM* dalvikJavaVMPtr; + +static JavaVM* runtimeJavaVMPtr; +static JNIEnv* runtimeJNIEnvPtr_GRAPHICS; +static JNIEnv* runtimeJNIEnvPtr_INPUT; +jclass class_CTCScreen; +jmethodID method_GetRGB; + +jclass class_CTCAndroidInput; +jmethodID method_ReceiveInput; + +jclass class_MainActivity; +jmethodID method_OpenLink; +jmethodID method_OpenPath; +jmethodID method_QuerySystemClipboard; +jmethodID method_PutClipboardData; + +jclass class_Frame; +jclass class_Rectangle; +jclass class_CTCClipboard = NULL; +jmethodID constructor_Rectangle; +jmethodID method_GetFrames; +jmethodID method_GetBounds; +jmethodID method_SetBounds; +jmethodID method_SystemClipboardDataReceived = NULL; + +jfieldID field_x; +jfieldID field_y; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + if (dalvikJavaVMPtr == NULL) { + //Save dalvik global JavaVM pointer + dalvikJavaVMPtr = vm; + JNIEnv *env = NULL; + (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4); + class_MainActivity = (*env)->NewGlobalRef(env,(*env)->FindClass(env, "pojlib/UnityPlayerActivity")); + method_OpenLink= (*env)->GetStaticMethodID(env, class_MainActivity, "openLink", "(Ljava/lang/String;)V"); + method_OpenPath= (*env)->GetStaticMethodID(env, class_MainActivity, "openLink", "(Ljava/lang/String;)V"); + method_QuerySystemClipboard = (*env)->GetStaticMethodID(env, class_MainActivity, "querySystemClipboard", "()V"); + method_PutClipboardData = (*env)->GetStaticMethodID(env, class_MainActivity, "putClipboardData", "(Ljava/lang/String;Ljava/lang/String;)V"); + } else if (dalvikJavaVMPtr != vm) { + runtimeJavaVMPtr = vm; + } + + return JNI_VERSION_1_4; +} + +JNIEXPORT void JNICALL Java_pojlib_input_AWTInputBridge_nativeSendData(JNIEnv* env, jclass clazz, jint type, jint i1, jint i2, jint i3, jint i4) { + if (runtimeJNIEnvPtr_INPUT == NULL) { + if (runtimeJavaVMPtr == NULL) { + return; + } else { + (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, &runtimeJNIEnvPtr_INPUT, NULL); + } + } + + if (method_ReceiveInput == NULL) { + class_CTCAndroidInput = (*runtimeJNIEnvPtr_INPUT)->FindClass(runtimeJNIEnvPtr_INPUT, "net/java/openjdk/cacio/ctc/CTCAndroidInput"); + if ((*runtimeJNIEnvPtr_INPUT)->ExceptionCheck(runtimeJNIEnvPtr_INPUT) == JNI_TRUE) { + (*runtimeJNIEnvPtr_INPUT)->ExceptionClear(runtimeJNIEnvPtr_INPUT); + class_CTCAndroidInput = (*runtimeJNIEnvPtr_INPUT)->FindClass(runtimeJNIEnvPtr_INPUT, "com/github/caciocavallosilano/cacio/ctc/CTCAndroidInput"); + } + assert(class_CTCAndroidInput != NULL); + method_ReceiveInput = (*runtimeJNIEnvPtr_INPUT)->GetStaticMethodID(runtimeJNIEnvPtr_INPUT, class_CTCAndroidInput, "receiveData", "(IIIII)V"); + assert(method_ReceiveInput != NULL); + } + (*runtimeJNIEnvPtr_INPUT)->CallStaticVoidMethod( + runtimeJNIEnvPtr_INPUT, + class_CTCAndroidInput, + method_ReceiveInput, + type, i1, i2, i3, i4 + ); +} + +// TODO: check for memory leaks +// int printed = 0; +int threadAttached = 0; +JNIEXPORT jintArray JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_renderAWTScreenFrame(JNIEnv* env, jclass clazz /*, jobject canvas, jint width, jint height */) { + if (runtimeJNIEnvPtr_GRAPHICS == NULL) { + if (runtimeJavaVMPtr == NULL) { + return NULL; + } else { + (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, &runtimeJNIEnvPtr_GRAPHICS, NULL); + } + } + + int *rgbArray; + jintArray jreRgbArray, androidRgbArray; + + if (method_GetRGB == NULL) { + class_CTCScreen = (*runtimeJNIEnvPtr_GRAPHICS)->FindClass(runtimeJNIEnvPtr_GRAPHICS, "net/java/openjdk/cacio/ctc/CTCScreen"); + if ((*runtimeJNIEnvPtr_GRAPHICS)->ExceptionCheck(runtimeJNIEnvPtr_GRAPHICS) == JNI_TRUE) { + (*runtimeJNIEnvPtr_GRAPHICS)->ExceptionClear(runtimeJNIEnvPtr_GRAPHICS); + class_CTCScreen = (*runtimeJNIEnvPtr_GRAPHICS)->FindClass(runtimeJNIEnvPtr_GRAPHICS, "com/github/caciocavallosilano/cacio/ctc/CTCScreen"); + } + assert(class_CTCScreen != NULL); + method_GetRGB = (*runtimeJNIEnvPtr_GRAPHICS)->GetStaticMethodID(runtimeJNIEnvPtr_GRAPHICS, class_CTCScreen, "getCurrentScreenRGB", "()[I"); + assert(method_GetRGB != NULL); + } + jreRgbArray = (jintArray) (*runtimeJNIEnvPtr_GRAPHICS)->CallStaticObjectMethod( + runtimeJNIEnvPtr_GRAPHICS, + class_CTCScreen, + method_GetRGB + ); + if (jreRgbArray == NULL) { + return NULL; + } + + // Copy JRE RGB array memory to Android. + int arrayLength = (*runtimeJNIEnvPtr_GRAPHICS)->GetArrayLength(runtimeJNIEnvPtr_GRAPHICS, jreRgbArray); + rgbArray = (*runtimeJNIEnvPtr_GRAPHICS)->GetIntArrayElements(runtimeJNIEnvPtr_GRAPHICS, jreRgbArray, 0); + androidRgbArray = (*env)->NewIntArray(env, arrayLength); + (*env)->SetIntArrayRegion(env, androidRgbArray, 0, arrayLength, rgbArray); + + (*runtimeJNIEnvPtr_GRAPHICS)->ReleaseIntArrayElements(runtimeJNIEnvPtr_GRAPHICS, jreRgbArray, rgbArray, 0); + // (*env)->DeleteLocalRef(env, androidRgbArray); + // free(rgbArray); + + return androidRgbArray; +} + +JNIEXPORT void JNICALL Java_net_java_openjdk_cacio_ctc_CTCClipboard_nQuerySystemClipboard(JNIEnv *env, jclass clazz) { + JNIEnv *dalvikEnv;char detachable = 0; + if((*dalvikJavaVMPtr)->GetEnv(dalvikJavaVMPtr, (void **) &dalvikEnv, JNI_VERSION_1_6) == JNI_EDETACHED) { + (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL); + detachable = 1; + } + if(method_SystemClipboardDataReceived == NULL) { + class_CTCClipboard = (*env)->NewGlobalRef(env, clazz); + method_SystemClipboardDataReceived = (*env)->GetStaticMethodID(env, clazz, "systemClipboardDataReceived", "(Ljava/lang/String;Ljava/lang/String;)V"); + } + (*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, class_MainActivity, method_QuerySystemClipboard); + if(detachable) (*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr); +} + +JNIEXPORT void JNICALL Java_net_java_openjdk_cacio_ctc_CTCClipboard_nPutClipboardData(JNIEnv* env, jclass clazz, jstring clipboardData, jstring clipboardDataMime) { + JNIEnv *dalvikEnv;char detachable = 0; + if((*dalvikJavaVMPtr)->GetEnv(dalvikJavaVMPtr, (void **) &dalvikEnv, JNI_VERSION_1_6) == JNI_EDETACHED) { + (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL); + detachable = 1; + } + + const char* dataChars = (*env)->GetStringUTFChars(env, clipboardData, NULL); + const char* mimeChars = (*env)->GetStringUTFChars(env, clipboardDataMime, NULL); + (*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, class_MainActivity, method_PutClipboardData, + (*dalvikEnv)->NewStringUTF(dalvikEnv, dataChars), + (*dalvikEnv)->NewStringUTF(dalvikEnv, mimeChars)); + (*env)->ReleaseStringUTFChars(env, clipboardData, dataChars); + (*env)->ReleaseStringUTFChars(env, clipboardDataMime, mimeChars); + if(detachable) (*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr); +} + +JNIEXPORT void JNICALL Java_com_github_caciocavallosilano_cacio_ctc_CTCClipboard_nQuerySystemClipboard(JNIEnv *env, jclass clazz) { + Java_net_java_openjdk_cacio_ctc_CTCClipboard_nQuerySystemClipboard(env, clazz); +} + +JNIEXPORT void JNICALL Java_com_github_caciocavallosilano_cacio_ctc_CTCClipboard_nPutClipboardData(JNIEnv* env, jclass clazz, jstring clipboardData, jstring clipboardDataMime) { + Java_net_java_openjdk_cacio_ctc_CTCClipboard_nPutClipboardData(env, clazz, clipboardData, clipboardDataMime); +} + +JNIEXPORT void JNICALL Java_net_java_openjdk_cacio_ctc_CTCDesktopPeer_openFile(JNIEnv *env, jclass clazz, jstring filePath) { + JNIEnv *dalvikEnv;char detachable = 0; + if((*dalvikJavaVMPtr)->GetEnv(dalvikJavaVMPtr, (void **) &dalvikEnv, JNI_VERSION_1_6) == JNI_EDETACHED) { + (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL); + detachable = 1; + } + const char* stringChars = (*env)->GetStringUTFChars(env, filePath, NULL); + (*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, class_MainActivity, method_OpenPath, (*dalvikEnv)->NewStringUTF(dalvikEnv, stringChars)); + (*env)->ReleaseStringUTFChars(env, filePath, stringChars); + if(detachable) (*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr); +} + +JNIEXPORT void JNICALL Java_net_java_openjdk_cacio_ctc_CTCDesktopPeer_openUri(JNIEnv *env, jclass clazz, jstring uri) { + JNIEnv *dalvikEnv;char detachable = 0; + if((*dalvikJavaVMPtr)->GetEnv(dalvikJavaVMPtr, (void **) &dalvikEnv, JNI_VERSION_1_6) == JNI_EDETACHED) { + (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL); + detachable = 1; + } + const char* stringChars = (*env)->GetStringUTFChars(env, uri, NULL); + (*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, class_MainActivity, method_OpenLink, (*dalvikEnv)->NewStringUTF(dalvikEnv, stringChars)); + (*env)->ReleaseStringUTFChars(env, uri, stringChars); + if(detachable) (*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr); +} + +JNIEXPORT void JNICALL Java_pojlib_input_AWTInputBridge_nativeClipboardReceived(JNIEnv *env, jclass clazz, jstring clipboardData, jstring clipboardDataMime) { + if(method_SystemClipboardDataReceived == NULL || class_CTCClipboard == NULL) return; + if (runtimeJNIEnvPtr_INPUT == NULL) { + if (runtimeJavaVMPtr == NULL) { + return; + } else { + (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, &runtimeJNIEnvPtr_INPUT, NULL); + } + } + const char* dataChars = clipboardData != NULL ? (*env)->GetStringUTFChars(env, clipboardData, NULL) : NULL; + const char* mimeChars = clipboardDataMime != NULL ? (*env)->GetStringUTFChars(env, clipboardDataMime, NULL) : NULL; + (*runtimeJNIEnvPtr_INPUT)->CallStaticVoidMethod(runtimeJNIEnvPtr_INPUT, class_CTCClipboard, method_SystemClipboardDataReceived, + clipboardData != NULL ? (*runtimeJNIEnvPtr_INPUT)->NewStringUTF(runtimeJNIEnvPtr_INPUT, dataChars) : NULL, + clipboardDataMime != NULL ? (*runtimeJNIEnvPtr_INPUT)->NewStringUTF(runtimeJNIEnvPtr_INPUT, mimeChars) : NULL); + if(dataChars != NULL) (*env)->ReleaseStringUTFChars(env, clipboardData, dataChars); + if(mimeChars != NULL) (*env)->ReleaseStringUTFChars(env, clipboardDataMime, mimeChars); +} + +JNIEXPORT void JNICALL +Java_pojlib_input_AWTInputBridge_nativeMoveWindow(JNIEnv *env, jclass clazz, jint xoff, jint yoff) { + if (runtimeJNIEnvPtr_INPUT == NULL) { + if (runtimeJavaVMPtr == NULL) { + return; + } else { + (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, &runtimeJNIEnvPtr_INPUT, NULL); + } + } + if(field_y == NULL) { + class_Frame = (*runtimeJNIEnvPtr_INPUT)->FindClass(runtimeJNIEnvPtr_INPUT, "java/awt/Frame"); + method_GetFrames = (*runtimeJNIEnvPtr_INPUT)->GetStaticMethodID(runtimeJNIEnvPtr_INPUT, class_Frame, "getFrames", "()[Ljava/awt/Frame;"); + method_GetBounds = (*runtimeJNIEnvPtr_INPUT)->GetMethodID(runtimeJNIEnvPtr_INPUT, class_Frame, "getBounds", "(Ljava/awt/Rectangle;)Ljava/awt/Rectangle;"); + method_SetBounds = (*runtimeJNIEnvPtr_INPUT)->GetMethodID(runtimeJNIEnvPtr_INPUT, class_Frame, "setBounds", "(Ljava/awt/Rectangle;)V"); + class_Rectangle = (*runtimeJNIEnvPtr_INPUT)->FindClass(runtimeJNIEnvPtr_INPUT, "java/awt/Rectangle"); + constructor_Rectangle = (*runtimeJNIEnvPtr_INPUT)->GetMethodID(runtimeJNIEnvPtr_INPUT, class_Rectangle, "", "()V"); + field_x = (*runtimeJNIEnvPtr_INPUT)->GetFieldID(runtimeJNIEnvPtr_INPUT, class_Rectangle, "x", "I"); + field_y = (*runtimeJNIEnvPtr_INPUT)->GetFieldID(runtimeJNIEnvPtr_INPUT, class_Rectangle, "y", "I"); + } + jobject rectangle = (*runtimeJNIEnvPtr_INPUT)->NewObject(runtimeJNIEnvPtr_INPUT, class_Rectangle, constructor_Rectangle); + jobjectArray frames = (*runtimeJNIEnvPtr_INPUT)->CallStaticObjectMethod(runtimeJNIEnvPtr_INPUT, class_Frame, method_GetFrames); + for(jsize i = 0; i < (*runtimeJNIEnvPtr_INPUT)->GetArrayLength(runtimeJNIEnvPtr_INPUT, frames); i++) { + jobject frame = (*runtimeJNIEnvPtr_INPUT)->GetObjectArrayElement(runtimeJNIEnvPtr_INPUT, frames, i); + (*runtimeJNIEnvPtr_INPUT)->CallObjectMethod(runtimeJNIEnvPtr_INPUT, frame, method_GetBounds, rectangle); + (*runtimeJNIEnvPtr_INPUT)->SetIntField(runtimeJNIEnvPtr_INPUT, rectangle, field_x, (*runtimeJNIEnvPtr_INPUT)->GetIntField(runtimeJNIEnvPtr_INPUT, rectangle, field_x) + xoff); + (*runtimeJNIEnvPtr_INPUT)->SetIntField(runtimeJNIEnvPtr_INPUT, rectangle, field_y, (*runtimeJNIEnvPtr_INPUT)->GetIntField(runtimeJNIEnvPtr_INPUT, rectangle, field_y) + yoff); + (*runtimeJNIEnvPtr_INPUT)->CallVoidMethod(runtimeJNIEnvPtr_INPUT, frame, method_SetBounds, rectangle); + (*runtimeJNIEnvPtr_INPUT)->DeleteLocalRef(runtimeJNIEnvPtr_INPUT, frame); + } + (*runtimeJNIEnvPtr_INPUT)->DeleteLocalRef(runtimeJNIEnvPtr_INPUT, rectangle); + (*runtimeJNIEnvPtr_INPUT)->DeleteLocalRef(runtimeJNIEnvPtr_INPUT, frames); +} \ No newline at end of file diff --git a/src/main/jni/egl_bridge.c b/src/main/jni/egl_bridge.c index b7d7db49..7999007a 100644 --- a/src/main/jni/egl_bridge.c +++ b/src/main/jni/egl_bridge.c @@ -166,9 +166,6 @@ void pojavSetWindowHint(int hint, int value) { // Stub } -void pojavPumpEvents(void* window) { - // Stub -} int32_t stride; void pojavSwapBuffers() { diff --git a/src/main/jni/environ/environ.c b/src/main/jni/environ/environ.c new file mode 100644 index 00000000..72633d78 --- /dev/null +++ b/src/main/jni/environ/environ.c @@ -0,0 +1,26 @@ +// +// Created by maks on 24.09.2022. +// + +#include +#include +#include +#include +#include "environ.h" +struct pojav_environ_s *pojav_environ; +__attribute__((constructor)) void env_init() { + char* strptr_env = getenv("POJAV_ENVIRON"); + if(strptr_env == NULL) { + __android_log_print(ANDROID_LOG_INFO, "Environ", "No environ found, creating..."); + pojav_environ = malloc(sizeof(struct pojav_environ_s)); + assert(pojav_environ); + memset(pojav_environ, 0 , sizeof(struct pojav_environ_s)); + if(asprintf(&strptr_env, "%p", pojav_environ) == -1) abort(); + setenv("POJAV_ENVIRON", strptr_env, 1); + free(strptr_env); + }else{ + __android_log_print(ANDROID_LOG_INFO, "Environ", "Found existing environ: %s", strptr_env); + pojav_environ = (void*) strtoul(strptr_env, NULL, 0x10); + } + __android_log_print(ANDROID_LOG_INFO, "Environ", "%p", pojav_environ); +} \ No newline at end of file diff --git a/src/main/jni/environ/environ.h b/src/main/jni/environ/environ.h new file mode 100644 index 00000000..3387cb2e --- /dev/null +++ b/src/main/jni/environ/environ.h @@ -0,0 +1,72 @@ +// +// Created by maks on 24.09.2022. +// + +#ifndef POJAVLAUNCHER_ENVIRON_H +#define POJAVLAUNCHER_ENVIRON_H + +#include +#include + +/* How many events can be handled at the same time */ +#define EVENT_WINDOW_SIZE 8000 + +typedef struct { + int type; + int i1; + int i2; + int i3; + int i4; +} GLFWInputEvent; + +typedef void GLFW_invoke_Char_func(void* window, unsigned int codepoint); +typedef void GLFW_invoke_CharMods_func(void* window, unsigned int codepoint, int mods); +typedef void GLFW_invoke_CursorEnter_func(void* window, int entered); +typedef void GLFW_invoke_CursorPos_func(void* window, double xpos, double ypos); +typedef void GLFW_invoke_FramebufferSize_func(void* window, int width, int height); +typedef void GLFW_invoke_Key_func(void* window, int key, int scancode, int action, int mods); +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 void GLFW_invoke_WindowSize_func(void* window, int width, int height); + +struct pojav_environ_s { + atomic_size_t eventCounter; // Count the number of events to be pumped out + GLFWInputEvent events[EVENT_WINDOW_SIZE]; + size_t outEventIndex; // Point to the current event that has yet to be pumped out to MC + size_t outTargetIndex; // Point to the newt index to stop by + size_t inEventIndex; // Point to the next event that has to be filled + size_t inEventCount; // Count registered right before pumping OUT events. Used as a cache. + double cursorX, cursorY, cLastX, cLastY; + jmethodID method_accessAndroidClipboard; + jmethodID method_onGrabStateChanged; + jmethodID method_glftSetWindowAttrib; + jmethodID method_internalWindowSizeChanged; + jclass bridgeClazz; + jclass vmGlfwClass; + jboolean isGrabbing; + jbyte* keyDownBuffer; + jbyte* mouseDownBuffer; + JavaVM* runtimeJavaVMPtr; + JNIEnv* runtimeJNIEnvPtr_JRE; + JavaVM* dalvikJavaVMPtr; + JNIEnv* dalvikJNIEnvPtr_ANDROID; + long showingWindow; + bool isInputReady, isCursorEntered, isUseStackQueueCall, shouldUpdateMouse; + int savedWidth, savedHeight; +#define ADD_CALLBACK_WWIN(NAME) \ + GLFW_invoke_##NAME##_func* GLFW_invoke_##NAME; + ADD_CALLBACK_WWIN(Char); + ADD_CALLBACK_WWIN(CharMods); + ADD_CALLBACK_WWIN(CursorEnter); + ADD_CALLBACK_WWIN(CursorPos); + ADD_CALLBACK_WWIN(FramebufferSize); + ADD_CALLBACK_WWIN(Key); + ADD_CALLBACK_WWIN(MouseButton); + ADD_CALLBACK_WWIN(Scroll); + ADD_CALLBACK_WWIN(WindowSize); + +#undef ADD_CALLBACK_WWIN +}; +extern struct pojav_environ_s *pojav_environ; + +#endif //POJAVLAUNCHER_ENVIRON_H diff --git a/src/main/jni/input_bridge_v3.c b/src/main/jni/input_bridge_v3.c index 9db31886..929006be 100644 --- a/src/main/jni/input_bridge_v3.c +++ b/src/main/jni/input_bridge_v3.c @@ -9,377 +9,615 @@ * * - Implements glfwSetCursorPos() to handle grab camera pos correctly. */ - -#include -#include + #include +#include +#include +#include +#include +#include +#include +#include #include "log.h" #include "utils.h" +#include "environ/environ.h" #define EVENT_TYPE_CHAR 1000 #define EVENT_TYPE_CHAR_MODS 1001 #define EVENT_TYPE_CURSOR_ENTER 1002 -#define EVENT_TYPE_CURSOR_POS 1003 #define EVENT_TYPE_FRAMEBUFFER_SIZE 1004 #define EVENT_TYPE_KEY 1005 #define EVENT_TYPE_MOUSE_BUTTON 1006 #define EVENT_TYPE_SCROLL 1007 #define EVENT_TYPE_WINDOW_SIZE 1008 -typedef void GLFW_invoke_Char_func(void* window, unsigned int codepoint); -typedef void GLFW_invoke_CharMods_func(void* window, unsigned int codepoint, int mods); -typedef void GLFW_invoke_CursorEnter_func(void* window, int entered); -typedef void GLFW_invoke_CursorPos_func(void* window, double xpos, double ypos); -typedef void GLFW_invoke_FramebufferSize_func(void* window, int width, int height); -typedef void GLFW_invoke_Key_func(void* window, int key, int scancode, int action, int mods); -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 void GLFW_invoke_WindowSize_func(void* window, int width, int height); - -static float grabCursorX, grabCursorY, lastCursorX, lastCursorY; - -jclass inputBridgeClass_ANDROID, inputBridgeClass_JRE; -jmethodID inputBridgeMethod_ANDROID, inputBridgeMethod_JRE; -jclass bridgeClazz; -jboolean isGrabbing; - -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - if (dalvikJavaVMPtr == NULL) { +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); + +jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { + if (pojav_environ->dalvikJavaVMPtr == NULL) { + __android_log_print(ANDROID_LOG_INFO, "Native", "Saving DVM environ..."); //Save dalvik global JavaVM pointer - 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); - isUseStackQueueCall = JNI_FALSE; - } else if (dalvikJavaVMPtr != vm) { - runtimeJavaVMPtr = vm; - (*vm)->GetEnv(vm, (void**) &runtimeJNIEnvPtr_JRE, JNI_VERSION_1_4); + 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"); + pojav_environ->isUseStackQueueCall = JNI_FALSE; + } else if (pojav_environ->dalvikJavaVMPtr != vm) { + __android_log_print(ANDROID_LOG_INFO, "Native", "Saving JVM environ..."); + pojav_environ->runtimeJavaVMPtr = vm; + (*vm)->GetEnv(vm, (void**) &pojav_environ->runtimeJNIEnvPtr_JRE, JNI_VERSION_1_4); + pojav_environ->vmGlfwClass = (*pojav_environ->runtimeJNIEnvPtr_JRE)->NewGlobalRef(pojav_environ->runtimeJNIEnvPtr_JRE, (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW")); + pojav_environ->method_glftSetWindowAttrib = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "glfwSetWindowAttrib", "(JII)V"); + pojav_environ->method_internalWindowSizeChanged = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "internalWindowSizeChanged", "(JII)V"); + jfieldID field_keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticFieldID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "keyDownBuffer", "Ljava/nio/ByteBuffer;"); + jobject keyDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_keyDownBuffer); + pojav_environ->keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, keyDownBufferJ); + jfieldID field_mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticFieldID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "mouseDownBuffer", "Ljava/nio/ByteBuffer;"); + jobject mouseDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_mouseDownBuffer); + pojav_environ->mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, mouseDownBufferJ); + hookExec(); + installLinkerBugMitigation(); + installEMUIIteratorMititgation(); } - - isGrabbing = JNI_FALSE; - - return JNI_VERSION_1_4; -} -void JNI_OnUnload(JavaVM* vm, void* reserved) { - dalvikJNIEnvPtr_JRE = NULL; - runtimeJNIEnvPtr_ANDROID = NULL; + if(pojav_environ->dalvikJavaVMPtr == vm) { + //perform in all DVM instances, not only during first ever set up + JNIEnv *env; + (*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4); + registerFunctions(env); + } + pojav_environ->isGrabbing = JNI_FALSE; + + return JNI_VERSION_1_4; } #define ADD_CALLBACK_WWIN(NAME) \ -GLFW_invoke_##NAME##_func* GLFW_invoke_##NAME; \ JNIEXPORT jlong JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSet##NAME##Callback(JNIEnv * env, jclass cls, jlong window, jlong callbackptr) { \ - void** oldCallback = (void**) &GLFW_invoke_##NAME; \ - GLFW_invoke_##NAME = (GLFW_invoke_##NAME##_func*) (uintptr_t) callbackptr; \ + void** oldCallback = (void**) &pojav_environ->GLFW_invoke_##NAME; \ + pojav_environ->GLFW_invoke_##NAME = (GLFW_invoke_##NAME##_func*) (uintptr_t) callbackptr; \ return (jlong) (uintptr_t) *oldCallback; \ } -ADD_CALLBACK_WWIN(Char); -ADD_CALLBACK_WWIN(CharMods); -ADD_CALLBACK_WWIN(CursorEnter); -ADD_CALLBACK_WWIN(CursorPos); -ADD_CALLBACK_WWIN(FramebufferSize); -ADD_CALLBACK_WWIN(Key); -ADD_CALLBACK_WWIN(MouseButton); -ADD_CALLBACK_WWIN(Scroll); -ADD_CALLBACK_WWIN(WindowSize); +ADD_CALLBACK_WWIN(Char) +ADD_CALLBACK_WWIN(CharMods) +ADD_CALLBACK_WWIN(CursorEnter) +ADD_CALLBACK_WWIN(CursorPos) +ADD_CALLBACK_WWIN(FramebufferSize) +ADD_CALLBACK_WWIN(Key) +ADD_CALLBACK_WWIN(MouseButton) +ADD_CALLBACK_WWIN(Scroll) +ADD_CALLBACK_WWIN(WindowSize) #undef ADD_CALLBACK_WWIN -jboolean attachThread(bool isAndroid, JNIEnv** secondJNIEnvPtr) { -#ifdef DEBUG - LOGD("Debug: Attaching %s thread to %s, javavm.isNull=%d\n", isAndroid ? "Android" : "JRE", isAndroid ? "JRE" : "Android", (isAndroid ? runtimeJavaVMPtr : dalvikJavaVMPtr) == NULL); -#endif +void handleFramebufferSizeJava(long window, int w, int h) { + (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, pojav_environ->method_internalWindowSizeChanged, (long)window, w, h); +} - if (*secondJNIEnvPtr != NULL || (!isUseStackQueueCall)) return JNI_TRUE; +void pojavPumpEvents(void* window) { + if(pojav_environ->shouldUpdateMouse) { + pojav_environ->GLFW_invoke_CursorPos(window, floor(pojav_environ->cursorX), + floor(pojav_environ->cursorY)); + } - if (isAndroid && runtimeJavaVMPtr) { - (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, secondJNIEnvPtr, NULL); - return JNI_TRUE; - } else if (!isAndroid && dalvikJavaVMPtr) { - (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, secondJNIEnvPtr, NULL); - return JNI_TRUE; + size_t index = pojav_environ->outEventIndex; + size_t targetIndex = pojav_environ->outTargetIndex; + + while (targetIndex != index) { + GLFWInputEvent event = pojav_environ->events[index]; + switch (event.type) { + case EVENT_TYPE_CHAR: + if(pojav_environ->GLFW_invoke_Char) pojav_environ->GLFW_invoke_Char(window, event.i1); + break; + case EVENT_TYPE_CHAR_MODS: + if(pojav_environ->GLFW_invoke_CharMods) pojav_environ->GLFW_invoke_CharMods(window, event.i1, event.i2); + break; + case EVENT_TYPE_KEY: + if(pojav_environ->GLFW_invoke_Key) pojav_environ->GLFW_invoke_Key(window, event.i1, event.i2, event.i3, event.i4); + break; + case EVENT_TYPE_MOUSE_BUTTON: + if(pojav_environ->GLFW_invoke_MouseButton) pojav_environ->GLFW_invoke_MouseButton(window, event.i1, event.i2, event.i3); + break; + case EVENT_TYPE_SCROLL: + if(pojav_environ->GLFW_invoke_Scroll) pojav_environ->GLFW_invoke_Scroll(window, event.i1, event.i2); + break; + case EVENT_TYPE_FRAMEBUFFER_SIZE: + handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2); + if(pojav_environ->GLFW_invoke_FramebufferSize) pojav_environ->GLFW_invoke_FramebufferSize(window, event.i1, event.i2); + break; + case EVENT_TYPE_WINDOW_SIZE: + handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2); + if(pojav_environ->GLFW_invoke_WindowSize) pojav_environ->GLFW_invoke_WindowSize(window, event.i1, event.i2); + break; + } + + index++; + if (index >= EVENT_WINDOW_SIZE) + index -= EVENT_WINDOW_SIZE; } - - return JNI_FALSE; + + // The out target index is updated by the rewinder } -void sendData(int type, int i1, int i2, int i3, int i4) { -#ifdef DEBUG - LOGD("Debug: Send data, jnienv.isNull=%d\n", runtimeJNIEnvPtr_ANDROID == NULL); -#endif - if (runtimeJNIEnvPtr_ANDROID == NULL) { - LOGE("BUG: Input is ready but thread is not attached yet."); - return; +/** Prepare the library for sending out callbacks to all windows */ +void pojavStartPumping() { + size_t counter = atomic_load_explicit(&pojav_environ->eventCounter, memory_order_acquire); + size_t index = pojav_environ->outEventIndex; + + unsigned targetIndex = index + counter; + if (targetIndex >= EVENT_WINDOW_SIZE) + targetIndex -= EVENT_WINDOW_SIZE; + + // Only accessed by one unique thread, no need for atomic store + pojav_environ->inEventCount = counter; + pojav_environ->outTargetIndex = targetIndex; + + //PumpEvents is called for every window, so this logic should be there in order to correctly distribute events to all windows. + if((pojav_environ->cLastX != pojav_environ->cursorX || pojav_environ->cLastY != pojav_environ->cursorY) && pojav_environ->GLFW_invoke_CursorPos) { + pojav_environ->cLastX = pojav_environ->cursorX; + pojav_environ->cLastY = pojav_environ->cursorY; + pojav_environ->shouldUpdateMouse = true; } - if(inputBridgeClass_ANDROID == NULL) return; - (*runtimeJNIEnvPtr_ANDROID)->CallStaticVoidMethod( - runtimeJNIEnvPtr_ANDROID, - inputBridgeClass_ANDROID, - inputBridgeMethod_ANDROID, - type, - i1, i2, i3, i4 - ); } -/** Setup the amount of event that will get pumped into each window */ -void pojavComputeEventTarget() { - // Stub -} +/** Prepare the library for the next round of new events */ +void pojavStopPumping() { + pojav_environ->outEventIndex = pojav_environ->outTargetIndex; -/** Apply index offsets after events have been pumped */ -void pojavRewindEvents() { - // Stub + // New events may have arrived while pumping, so remove only the difference before the start and end of execution + atomic_fetch_sub_explicit(&pojav_environ->eventCounter, pojav_environ->inEventCount, memory_order_acquire); + // Make sure the next frame won't send mouse updates if it's unnecessary + pojav_environ->shouldUpdateMouse = false; } JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwGetCursorPos(JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window, jobject xpos, jobject ypos) { - // Stub + *(double*)(*env)->GetDirectBufferAddress(env, xpos) = pojav_environ->cursorX; + *(double*)(*env)->GetDirectBufferAddress(env, ypos) = pojav_environ->cursorY; } JNIEXPORT void JNICALL JavaCritical_org_lwjgl_glfw_GLFW_nglfwGetCursorPosA(__attribute__((unused)) jlong window, jint lengthx, jdouble* xpos, jint lengthy, jdouble* ypos) { - // Stub + *xpos = pojav_environ->cursorX; + *ypos = pojav_environ->cursorY; } JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwGetCursorPosA(JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window, jdoubleArray xpos, jdoubleArray ypos) { - // Stub + (*env)->SetDoubleArrayRegion(env, xpos, 0,1, &pojav_environ->cursorX); + (*env)->SetDoubleArrayRegion(env, ypos, 0,1, &pojav_environ->cursorY); } JNIEXPORT void JNICALL JavaCritical_org_lwjgl_glfw_GLFW_glfwSetCursorPos(__attribute__((unused)) jlong window, jdouble xpos, jdouble ypos) { - // Stub + pojav_environ->cLastX = pojav_environ->cursorX = xpos; + pojav_environ->cLastY = pojav_environ->cursorY = ypos; } JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_glfwSetCursorPos(__attribute__((unused)) JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window, jdouble xpos, jdouble ypos) { - // Stub + JavaCritical_org_lwjgl_glfw_GLFW_glfwSetCursorPos(window, xpos, ypos); } -void closeGLFWWindow() { - exit(-1); + + +void sendData(int type, int i1, int i2, int i3, int i4) { + GLFWInputEvent *event = &pojav_environ->events[pojav_environ->inEventIndex]; + event->type = type; + event->i1 = i1; + event->i2 = i2; + event->i3 = i3; + event->i4 = i4; + + if (++pojav_environ->inEventIndex >= EVENT_WINDOW_SIZE) + pojav_environ->inEventIndex -= EVENT_WINDOW_SIZE; + + atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeAttachThreadToOther(JNIEnv* env, jclass clazz, jboolean isAndroid, jboolean isUseStackQueueBool) { -#ifdef DEBUG - LOGD("Debug: JNI attaching thread, isUseStackQueue=%d\n", isUseStackQueue); -#endif +/** + * Hooked version of java.lang.UNIXProcess.forkAndExec() + * which is used to handle the "open" command. + */ +jint +hooked_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) { + char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); + + // Here we only handle the "xdg-open" command + if (strcmp(basename(pProg), "xdg-open") != 0) { + (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); + return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); + } + (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - jboolean result; + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); + return 0; +} - isUseStackQueueCall = (int) isUseStackQueueBool; - if (isAndroid) { - result = attachThread(true, &runtimeJNIEnvPtr_ANDROID); +void hookExec() { + jclass cls; + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec"); + if (!orig_ProcessImpl_forkAndExec) { + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec"); + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl"); } else { - result = attachThread(false, &dalvikJNIEnvPtr_JRE); + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess"); } - - if (isUseStackQueueCall && isAndroid && result) { - isPrepareGrabPos = true; + JNINativeMethod methods[] = { + {"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec} + }; + (*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1); + printf("Registered forkAndExec\n"); +} + +/** + * Basically a verbatim implementation of ndlopen(), found at + * https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11 + * The idea is that since, on Android 10 and earlier, the linker doesn't really do namespace nesting. + * It is not a problem as most of the libraries are in the launcher path, but when you try to run + * VulkanMod which loads shaderc outside of the default jni libs directory through this method, + * it can't load it because the path is not in the allowed paths for the anonymous namesapce. + * This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace + */ +jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, + __attribute__((unused)) jclass class, + jlong filename_ptr, + jint jmode) { + const char* filename = (const char*) filename_ptr; + int mode = (int)jmode; + return (jlong) dlopen(filename, mode); +} + +/** + * Install the linker bug mitigation for Android 10 and lower. Fixes VulkanMod crashing on these + * Android versions due to missing namespace nesting. + */ +void installLinkerBugMitigation() { + if(android_get_device_api_level() >= 30) return; + __android_log_print(ANDROID_LOG_INFO, "Api29LinkerFix", "API < 30 detected, installing linker bug mitigation"); + JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; + jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); + if(dynamicLinkLoader == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to find the target class"); + (*env)->ExceptionClear(env); + return; + } + JNINativeMethod ndlopenMethod[] = { + {"ndlopen", "(JI)J", &ndlopen_bugfix} + }; + if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to register the bugfix method"); + (*env)->ExceptionClear(env); } - - return result; } -JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jstring copySrc) { +/** + * This function is meant as a substitute for SharedLibraryUtil.getLibraryPath() that just returns 0 + * (thus making the parent Java function return null). This is done to avoid using the LWJGL's default function, + * which will hang the crappy EMUI linker by dlopen()ing inside of dl_iterate_phdr(). + * @return 0, to make the parent Java function return null immediately. + * For reference: https://github.com/PojavLauncherTeam/lwjgl3/blob/fix_huawei_hang/modules/lwjgl/core/src/main/java/org/lwjgl/system/SharedLibraryUtil.java + */ +jint getLibraryPath_fix(__attribute__((unused)) JNIEnv *env, + __attribute__((unused)) jclass class, + __attribute__((unused)) jlong pLibAddress, + __attribute__((unused)) jlong sOutAddress, + __attribute__((unused)) jint bufSize){ + return 0; +} + +/** + * Install the linker hang mitigation that is meant to prevent linker hangs on old EMUI firmware. + */ +void installEMUIIteratorMititgation() { + if(getenv("POJAV_EMUI_ITERATOR_MITIGATE") == NULL) return; + __android_log_print(ANDROID_LOG_INFO, "EMUIIteratorFix", "Installing..."); + JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; + jclass sharedLibraryUtil = (*env)->FindClass(env, "org/lwjgl/system/SharedLibraryUtil"); + if(sharedLibraryUtil == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to find the target class"); + (*env)->ExceptionClear(env); + return; + } + JNINativeMethod getLibraryPathMethod[] = { + {"getLibraryPath", "(JJI)I", &getLibraryPath_fix} + }; + if((*env)->RegisterNatives(env, sharedLibraryUtil, getLibraryPathMethod, 1) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to register the mitigation method"); + (*env)->ExceptionClear(env); + } +} + +void critical_set_stackqueue(jboolean use_input_stack_queue) { + pojav_environ->isUseStackQueueCall = (int) use_input_stack_queue; +} + +void noncritical_set_stackqueue(__attribute__((unused)) JNIEnv *env, __attribute__((unused)) jclass clazz, jboolean use_input_stack_queue) { + critical_set_stackqueue(use_input_stack_queue); +} + +JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, __attribute__((unused)) jclass clazz, jint action, jbyteArray copySrc) { #ifdef DEBUG - LOGD("Debug: Clipboard access is going on\n", isUseStackQueueCall); + LOGD("Debug: Clipboard access is going on\n", pojav_environ->isUseStackQueueCall); #endif JNIEnv *dalvikEnv; - (*dalvikJavaVMPtr)->AttachCurrentThread(dalvikJavaVMPtr, &dalvikEnv, NULL); + (*pojav_environ->dalvikJavaVMPtr)->AttachCurrentThread(pojav_environ->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); - + assert(pojav_environ->bridgeClazz != NULL); + LOGD("Clipboard: Converting string\n"); - jstring copyDst = convertStringJVM(env, dalvikEnv, copySrc); + char *copySrcC; + jstring copyDst = NULL; + if (copySrc) { + copySrcC = (char *)((*env)->GetByteArrayElements(env, copySrc, NULL)); + copyDst = (*dalvikEnv)->NewStringUTF(dalvikEnv, copySrcC); + } + LOGD("Clipboard: Calling 2nd\n"); - jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, bridgeClazz, bridgeMethod, action, copyDst)); - (*dalvikJavaVMPtr)->DetachCurrentThread(dalvikJavaVMPtr); + jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, pojav_environ->bridgeClazz, pojav_environ->method_accessAndroidClipboard, action, copyDst)); + + if (copySrc) { + (*dalvikEnv)->DeleteLocalRef(dalvikEnv, copyDst); + (*env)->ReleaseByteArrayElements(env, copySrc, (jbyte *)copySrcC, 0); + } + (*pojav_environ->dalvikJavaVMPtr)->DetachCurrentThread(pojav_environ->dalvikJavaVMPtr); return pasteDst; } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(JNIEnv* env, jclass clazz, jboolean inputReady) { +JNIEXPORT jboolean JNICALL JavaCritical_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(jboolean inputReady) { #ifdef DEBUG - LOGD("Debug: Changing input state, isReady=%d, isUseStackQueueCall=%d\n", inputReady, isUseStackQueueCall); + LOGD("Debug: Changing input state, isReady=%d, pojav_environ->isUseStackQueueCall=%d\n", inputReady, pojav_environ->isUseStackQueueCall); #endif - isInputReady = inputReady; - return isUseStackQueueCall; + __android_log_print(ANDROID_LOG_INFO, "NativeInput", "Input ready: %i", inputReady); + pojav_environ->isInputReady = inputReady; + return pojav_environ->isUseStackQueueCall; } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetGrabbing(JNIEnv* env, jclass clazz, jboolean grabbing, jint xset, jint yset) { - isGrabbing = grabbing; - if (isGrabbing == JNI_TRUE) { - grabCursorX = xset; // savedWidth / 2; - grabCursorY = yset; // savedHeight / 2; - isPrepareGrabPos = true; - } +JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jboolean inputReady) { + return JavaCritical_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(inputReady); } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeIsGrabbing(JNIEnv* env, jclass clazz) { - return isGrabbing; +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); + pojav_environ->isGrabbing = grabbing; } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendChar(JNIEnv* env, jclass clazz, jchar codepoint /* jint codepoint */) { - if (GLFW_invoke_Char && isInputReady) { - if (isUseStackQueueCall) { +jboolean critical_send_char(jchar codepoint) { + if (pojav_environ->GLFW_invoke_Char && pojav_environ->isInputReady) { + if (pojav_environ->isUseStackQueueCall) { sendData(EVENT_TYPE_CHAR, codepoint, 0, 0, 0); } else { - GLFW_invoke_Char((void*) showingWindow, (unsigned int) codepoint); - // return lwjgl2_triggerCharEvent(codepoint); + pojav_environ->GLFW_invoke_Char((void*) pojav_environ->showingWindow, (unsigned int) codepoint); } return JNI_TRUE; } return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCharMods(JNIEnv* env, jclass clazz, jchar codepoint, jint mods) { - if (GLFW_invoke_CharMods && isInputReady) { - if (isUseStackQueueCall) { - sendData(EVENT_TYPE_CHAR_MODS, (unsigned int) codepoint, mods, 0, 0); +jboolean noncritical_send_char(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jchar codepoint) { + return critical_send_char(codepoint); +} + +jboolean critical_send_char_mods(jchar codepoint, jint mods) { + if (pojav_environ->GLFW_invoke_CharMods && pojav_environ->isInputReady) { + if (pojav_environ->isUseStackQueueCall) { + sendData(EVENT_TYPE_CHAR_MODS, (int) codepoint, mods, 0, 0); } else { - GLFW_invoke_CharMods((void*) showingWindow, codepoint, mods); + pojav_environ->GLFW_invoke_CharMods((void*) pojav_environ->showingWindow, codepoint, mods); } return JNI_TRUE; } return JNI_FALSE; } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorPos(JNIEnv* env, jclass clazz, jfloat x, jfloat y) { - if (GLFW_invoke_CursorPos && isInputReady) { - if (!isCursorEntered) { - if (GLFW_invoke_CursorEnter) { - isCursorEntered = true; - if (isUseStackQueueCall) { +jboolean noncritical_send_char_mods(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jchar codepoint, jint mods) { + return critical_send_char_mods(codepoint, mods); +} +/* +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorEnter(JNIEnv* env, jclass clazz, jint entered) { + if (pojav_environ->GLFW_invoke_CursorEnter && pojav_environ->isInputReady) { + pojav_environ->GLFW_invoke_CursorEnter(pojav_environ->showingWindow, entered); + } +} +*/ + +void critical_send_cursor_pos(jfloat x, jfloat y) { +#ifdef DEBUG + LOGD("Sending cursor position \n"); +#endif + if (pojav_environ->GLFW_invoke_CursorPos && pojav_environ->isInputReady) { +#ifdef DEBUG + LOGD("pojav_environ->GLFW_invoke_CursorPos && pojav_environ->isInputReady \n"); +#endif + if (!pojav_environ->isCursorEntered) { + if (pojav_environ->GLFW_invoke_CursorEnter) { + pojav_environ->isCursorEntered = true; + if (pojav_environ->isUseStackQueueCall) { sendData(EVENT_TYPE_CURSOR_ENTER, 1, 0, 0, 0); } else { - GLFW_invoke_CursorEnter((void*) showingWindow, 1); + pojav_environ->GLFW_invoke_CursorEnter((void*) pojav_environ->showingWindow, 1); } - } else if (isGrabbing) { + } else if (pojav_environ->isGrabbing) { // Some Minecraft versions does not use GLFWCursorEnterCallback // This is a smart check, as Minecraft will not in grab mode if already not. - isCursorEntered = true; + pojav_environ->isCursorEntered = true; } } - if (isGrabbing) { - if (!isPrepareGrabPos) { - grabCursorX += x - lastCursorX; - grabCursorY += y - lastCursorY; - } - - lastCursorX = x; - lastCursorY = y; - - if (isPrepareGrabPos) { - isPrepareGrabPos = false; - return; - } - } - - if (!isUseStackQueueCall) { - GLFW_invoke_CursorPos((void*) showingWindow, (double) (x), (double) (y)); + if (!pojav_environ->isUseStackQueueCall) { + pojav_environ->GLFW_invoke_CursorPos((void*) pojav_environ->showingWindow, (double) (x), (double) (y)); } else { - sendData(EVENT_TYPE_CURSOR_POS, (isGrabbing ? grabCursorX : x), (isGrabbing ? grabCursorY : y), 0, 0); + pojav_environ->cursorX = x; + pojav_environ->cursorY = y; } - - lastCursorX = x; - lastCursorY = y; } } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendKey(JNIEnv* env, jclass clazz, jint key, jint scancode, jint action, jint mods) { - if (GLFW_invoke_Key && isInputReady) { - if (isUseStackQueueCall) { +void noncritical_send_cursor_pos(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jfloat x, jfloat y) { + critical_send_cursor_pos(x, y); +} +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +void critical_send_key(jint key, jint scancode, jint action, jint mods) { + if (pojav_environ->GLFW_invoke_Key && pojav_environ->isInputReady) { + pojav_environ->keyDownBuffer[max(0, key-31)] = (jbyte) action; + if (pojav_environ->isUseStackQueueCall) { sendData(EVENT_TYPE_KEY, key, scancode, action, mods); } else { - GLFW_invoke_Key((void*) showingWindow, key, scancode, action, mods); + pojav_environ->GLFW_invoke_Key((void*) pojav_environ->showingWindow, key, scancode, action, mods); } } } +void noncritical_send_key(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint key, jint scancode, jint action, jint mods) { + critical_send_key(key, scancode, action, mods); +} - -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendMouseButton(JNIEnv* env, jclass clazz, jint button, jint action, jint mods) { - if (isInputReady) { - if (button == -1) { - // Notify to prepare set new grab pos - isPrepareGrabPos = true; - } else if (GLFW_invoke_MouseButton) { - if (isUseStackQueueCall) { - sendData(EVENT_TYPE_MOUSE_BUTTON, button, action, mods, 0); - } else { - GLFW_invoke_MouseButton((void*) showingWindow, button, action, mods); - } +void critical_send_mouse_button(jint button, jint action, jint mods) { + if (pojav_environ->GLFW_invoke_MouseButton && pojav_environ->isInputReady) { + pojav_environ->mouseDownBuffer[max(0, button)] = (jbyte) action; + if (pojav_environ->isUseStackQueueCall) { + sendData(EVENT_TYPE_MOUSE_BUTTON, button, action, mods, 0); + } else { + pojav_environ->GLFW_invoke_MouseButton((void*) pojav_environ->showingWindow, button, action, mods); } } } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendScreenSize(JNIEnv* env, jclass clazz, jint width, jint height) { - savedWidth = width; - savedHeight = height; - - if (isInputReady) { - if (GLFW_invoke_FramebufferSize) { - if (isUseStackQueueCall) { +void noncritical_send_mouse_button(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint button, jint action, jint mods) { + critical_send_mouse_button(button, action, mods); +} + +void critical_send_screen_size(jint width, jint height) { + pojav_environ->savedWidth = width; + pojav_environ->savedHeight = height; + if (pojav_environ->isInputReady) { + if (pojav_environ->GLFW_invoke_FramebufferSize) { + if (pojav_environ->isUseStackQueueCall) { sendData(EVENT_TYPE_FRAMEBUFFER_SIZE, width, height, 0, 0); } else { - GLFW_invoke_FramebufferSize((void*) showingWindow, width, height); + pojav_environ->GLFW_invoke_FramebufferSize((void*) pojav_environ->showingWindow, width, height); } } - - if (GLFW_invoke_WindowSize) { - if (isUseStackQueueCall) { + + if (pojav_environ->GLFW_invoke_WindowSize) { + if (pojav_environ->isUseStackQueueCall) { sendData(EVENT_TYPE_WINDOW_SIZE, width, height, 0, 0); } else { - GLFW_invoke_WindowSize((void*) showingWindow, width, height); + pojav_environ->GLFW_invoke_WindowSize((void*) pojav_environ->showingWindow, width, height); } } } - - // return (isInputReady && (GLFW_invoke_FramebufferSize || GLFW_invoke_WindowSize)); } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendScroll(JNIEnv* env, jclass clazz, jdouble xoffset, jdouble yoffset) { - if (GLFW_invoke_Scroll && isInputReady) { - if (isUseStackQueueCall) { - sendData(EVENT_TYPE_SCROLL, xoffset, yoffset, 0, 0); +void noncritical_send_screen_size(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint width, jint height) { + critical_send_screen_size(width, height); +} + +void critical_send_scroll(jdouble xoffset, jdouble yoffset) { + if (pojav_environ->GLFW_invoke_Scroll && pojav_environ->isInputReady) { + if (pojav_environ->isUseStackQueueCall) { + sendData(EVENT_TYPE_SCROLL, (int)xoffset, (int)yoffset, 0, 0); } else { - GLFW_invoke_Scroll((void*) showingWindow, (double) xoffset, (double) yoffset); + pojav_environ->GLFW_invoke_Scroll((void*) pojav_environ->showingWindow, (double) xoffset, (double) yoffset); } } } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(JNIEnv* env, jclass clazz, jlong window) { - showingWindow = (long) window; +void noncritical_send_scroll(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jdouble xoffset, jdouble yoffset) { + critical_send_scroll(xoffset, yoffset); } -JNIEXPORT void JNICALL -Java_org_lwjgl_glfw_CallbackBridge_setClass(JNIEnv *env, jclass clazz) { - inputBridgeMethod_ANDROID = (*env)->GetStaticMethodID(env, clazz, "receiveCallback", "(IIIII)V"); - inputBridgeClass_ANDROID = (*env)->NewGlobalRef(env, clazz); + +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jlong window) { + pojav_environ->showingWindow = (long) window; } -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(JNIEnv* env, jclass clazz, jint attrib, jint value) { - if (!showingWindow) { - return; // nothing to do yet +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint attrib, jint value) { + if (!pojav_environ->showingWindow || !pojav_environ->isUseStackQueueCall) { + // If the window is not shown, there is nothing to do yet. + // For Minecraft < 1.13, calling to JNI functions here crashes the JVM for some reason, therefore it is skipped for now. + return; } - jclass glfwClazz = (*runtimeJNIEnvPtr_ANDROID)->FindClass(runtimeJNIEnvPtr_ANDROID, "org/lwjgl/glfw/GLFW"); - assert(glfwClazz != NULL); - jmethodID glfwMethod = (*runtimeJNIEnvPtr_ANDROID)->GetStaticMethodID(runtimeJNIEnvPtr_ANDROID, glfwMethod, "glfwSetWindowAttrib", "(JII)V"); - assert(glfwMethod != NULL); - - (*runtimeJNIEnvPtr_ANDROID)->CallStaticVoidMethod( - runtimeJNIEnvPtr_ANDROID, - glfwClazz, glfwMethod, - (jlong) showingWindow, attrib, value + (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod( + pojav_environ->runtimeJNIEnvPtr_JRE, + pojav_environ->vmGlfwClass, pojav_environ->method_glftSetWindowAttrib, + (jlong) pojav_environ->showingWindow, attrib, value ); } +const static JNINativeMethod critical_fcns[] = { + {"nativeSetUseInputStackQueue", "(Z)V", critical_set_stackqueue}, + {"nativeSendChar", "(C)Z", critical_send_char}, + {"nativeSendCharMods", "(CI)Z", critical_send_char_mods}, + {"nativeSendKey", "(IIII)V", critical_send_key}, + {"nativeSendCursorPos", "(FF)V", critical_send_cursor_pos}, + {"nativeSendMouseButton", "(III)V", critical_send_mouse_button}, + {"nativeSendScroll", "(DD)V", critical_send_scroll}, + {"nativeSendScreenSize", "(II)V", critical_send_screen_size} +}; + +const static JNINativeMethod noncritical_fcns[] = { + {"nativeSetUseInputStackQueue", "(Z)V", noncritical_set_stackqueue}, + {"nativeSendChar", "(C)Z", noncritical_send_char}, + {"nativeSendCharMods", "(CI)Z", noncritical_send_char_mods}, + {"nativeSendKey", "(IIII)V", noncritical_send_key}, + {"nativeSendCursorPos", "(FF)V", noncritical_send_cursor_pos}, + {"nativeSendMouseButton", "(III)V", noncritical_send_mouse_button}, + {"nativeSendScroll", "(DD)V", noncritical_send_scroll}, + {"nativeSendScreenSize", "(II)V", noncritical_send_screen_size} +}; + + +static bool criticalNativeAvailable; + +void dvm_testCriticalNative(void* arg0, void* arg1, void* arg2, void* arg3) { + if(arg0 != 0 && arg2 == 0 && arg3 == 0) { + criticalNativeAvailable = false; + }else if (arg0 == 0 && arg1 == 0){ + criticalNativeAvailable = true; + }else { + criticalNativeAvailable = false; // just to be safe + } +} + +static bool tryCriticalNative(JNIEnv *env) { + static const JNINativeMethod testJNIMethod[] = { + { "testCriticalNative", "(II)V", dvm_testCriticalNative} + }; + jclass criticalNativeTest = (*env)->FindClass(env, "pojlib/input/CriticalNativeTest"); + if(criticalNativeTest == NULL) { + (*env)->ExceptionClear(env); + return false; + } + jmethodID criticalNativeTestMethod = (*env)->GetStaticMethodID(env, criticalNativeTest, "invokeTest", "()V"); + (*env)->RegisterNatives(env, criticalNativeTest, testJNIMethod, 1); + (*env)->CallStaticVoidMethod(env, criticalNativeTest, criticalNativeTestMethod); + (*env)->UnregisterNatives(env, criticalNativeTest); + return criticalNativeAvailable; +} +static void registerFunctions(JNIEnv *env) { + bool use_critical_cc = tryCriticalNative(env); + jclass bridge_class = (*env)->FindClass(env, "org/lwjgl/glfw/CallbackBridge"); + if(use_critical_cc) { + __android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is available. Enjoy the 4.6x times faster input!"); + }else{ + __android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is not available. Upgrade, maybe?"); + } + (*env)->RegisterNatives(env, + bridge_class, + use_critical_cc ? critical_fcns : noncritical_fcns, + sizeof(critical_fcns)/sizeof(critical_fcns[0])); +} \ No newline at end of file diff --git a/src/main/jni/utils.h b/src/main/jni/utils.h index 1a0c8bce..31d94a40 100644 --- a/src/main/jni/utils.h +++ b/src/main/jni/utils.h @@ -22,3 +22,7 @@ jobjectArray convert_from_char_array(JNIEnv *env, char **charArray, int num_rows void free_char_array(JNIEnv *env, jobjectArray jstringArray, const char **charArray); jstring convertStringJVM(JNIEnv* srcEnv, JNIEnv* dstEnv, jstring srcStr); +void hookExec(); +void installLinkerBugMitigation(); +void installEMUIIteratorMititgation(); +JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc); \ No newline at end of file diff --git a/src/main/jniLibs/arm64-v8a/libopuscodec.so b/src/main/jniLibs/arm64-v8a/libopuscodec.so deleted file mode 100644 index 73ea928e..00000000 --- a/src/main/jniLibs/arm64-v8a/libopuscodec.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f762fdda76edf85b7b1807f1b56d97063eed93b2277586bc7e7f2f43a5a540a9 -size 663888