Skip to content

Commit

Permalink
Implement Pojav Input System (#59)
Browse files Browse the repository at this point in the history
* Implement Pojav Input Backend

* Add Gamepad Compat

* Integrate Gamepad Input

---------

Co-authored-by: Maksim Belov <[email protected]>
Co-authored-by: Mathias Boulay <[email protected]>
Co-authored-by: Duy Tran Khanh <[email protected]>
  • Loading branch information
4 people authored Sep 22, 2024
1 parent 0a13549 commit 2dc9124
Show file tree
Hide file tree
Showing 38 changed files with 2,796 additions and 280 deletions.
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ android {
compileSdkVersion = "android-32"
defaultConfig {
minSdkVersion(29)
multiDexEnabled true

ndk.stl = "c++_shared"
}
Expand All @@ -30,7 +31,7 @@ android {
}

build {
finalizedBy(':wrapper:launcher:build')
//finalizedBy(':wrapper:launcher:build')
}

dependencies {
Expand All @@ -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")
}
4 changes: 2 additions & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencyResolutionManagement {
google()
mavenCentral()
maven {
url = uri("https://repo.u-team.info")
url = uri("https://jitpack.io")
}
flatDir {
dirs("libs")
Expand All @@ -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"

Binary file modified src/main/assets/lwjgl/lwjgl-glfw-classes.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion src/main/assets/lwjgl/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1726431237721
1725986487354
12 changes: 12 additions & 0 deletions src/main/java/dalvik/annotation/optimization/CriticalNative.java
Original file line number Diff line number Diff line change
@@ -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 {
}
152 changes: 111 additions & 41 deletions src/main/java/org/lwjgl/glfw/CallbackBridge.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,51 @@
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<GrabListener> 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);
}


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);
Expand Down Expand Up @@ -82,57 +80,129 @@ 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);
}

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;
}
}


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");
}
}

1 change: 1 addition & 0 deletions src/main/java/pojlib/API.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/main/java/pojlib/InstanceHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 2dc9124

Please sign in to comment.