Merge pull request #51 from AngelAuraMC/fix/revert-forced-grabbing

fix(Pointer/Capture): Make non-opportunistic grabbing opt-in
This commit is contained in:
alexytomi 2025-07-31 15:23:56 +08:00 committed by GitHub
commit 974a039c07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 112 additions and 8 deletions

View File

@ -1,6 +1,7 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MainActivity.touchCharInput;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
@ -193,6 +194,13 @@ public class MinecraftGLSurface extends View implements GrabListener, DirectGame
if(toolType == MotionEvent.TOOL_TYPE_MOUSE) {
if(Tools.isAndroid8OrHigher() &&
mPointerCapture != null) {
// Can't handleAutomaticCapture if mouse isn't captured
if (!CallbackBridge.isGrabbing() // Only capture if not in menu and user said so
&& !PREF_MOUSE_GRAB_FORCE) {
// This returns true but we really can't consume this.
// Else we don't receive ACTION_MOVE
return !dispatchGenericMotionEvent(e);
}
mPointerCapture.handleAutomaticCapture();
return true;
}
@ -241,9 +249,9 @@ public class MinecraftGLSurface extends View implements GrabListener, DirectGame
// Make sure we grabbed the mouse if necessary
updateGrabState(CallbackBridge.isGrabbing());
switch(event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * LauncherPreferences.PREF_SCALE_FACTOR);
CallbackBridge.mouseY = (event.getY(mouseCursorIndex) * LauncherPreferences.PREF_SCALE_FACTOR);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);

View File

@ -35,6 +35,7 @@ import android.provider.OpenableColumns;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.InputDevice;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
@ -1557,4 +1558,19 @@ public final class Tools {
}
return Integer.parseInt(iVersionArray[0] + iVersionArray[1] + iVersionArray[2]);
}
public static boolean isPointerDeviceConnected() {
int[] deviceIds = InputDevice.getDeviceIds();
for (int id : deviceIds) {
InputDevice device = InputDevice.getDevice(id);
if (device == null) continue;
int sources = device.getSources();
if ((sources & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE
|| (sources & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD
|| (sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
return true;
}
}
return false;
}
}

View File

@ -1,13 +1,20 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import net.kdt.pojavlaunch.GrabListener;
import net.kdt.pojavlaunch.MinecraftGLSurface;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
@ -15,7 +22,7 @@ import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge;
@RequiresApi(api = Build.VERSION_CODES.O)
public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChangeListener, View.OnCapturedPointerListener {
public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChangeListener, View.OnCapturedPointerListener, GrabListener, SharedPreferences.OnSharedPreferenceChangeListener {
private static final float TOUCHPAD_SCROLL_THRESHOLD = 1;
private final AbstractTouchpad mTouchpad;
private final View mHostView;
@ -32,14 +39,46 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
this.mHostView = hostView;
hostView.setOnCapturedPointerListener(this);
hostView.getViewTreeObserver().addOnWindowFocusChangeListener(this);
DEFAULT_PREF.registerOnSharedPreferenceChangeListener(this);
if (!PREF_MOUSE_GRAB_FORCE)
CallbackBridge.addGrabListener(this);
}
private void enableTouchpadIfNecessary() {
if(!mTouchpad.getDisplayState()) mTouchpad.enable(true);
if(!mTouchpad.getDisplayState() && PREF_MOUSE_GRAB_FORCE) mTouchpad.enable(true);
}
// Needed so it releases the cursor when inside game menu
@Override
public void onGrabState(boolean isGrabbing) {
updateCursorState(isGrabbing);
}
// It's only here so the side-dialog changes it live
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
updateCursorState(CallbackBridge.isGrabbing());
}
private void updateCursorState(boolean isGrabbing) {
if (!isGrabbing // Only capture if not in menu and user said so
&& !PREF_MOUSE_GRAB_FORCE) {
mHostView.releasePointerCapture(); // Release the capture since user said so
}
// Capture the pointer when not inside a menu
// So user doesn't have to click to get rid of cursor
handleAutomaticCapture();
}
public void handleAutomaticCapture() {
if(!mHostView.hasWindowFocus()) {
if (!CallbackBridge.isGrabbing() // Only capture if not in menu and user said so
&& !PREF_MOUSE_GRAB_FORCE) {
mTouchpad.disable();
return;
}
if (mHostView.hasPointerCapture()) {
enableTouchpadIfNecessary();
return;
}
if (!mHostView.hasWindowFocus()) {
mHostView.requestFocus();
} else {
mHostView.requestPointerCapture();
@ -128,7 +167,11 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if(hasFocus && Tools.isAndroid8OrHigher()) mHostView.requestPointerCapture();
if (!CallbackBridge.isGrabbing() // Only capture if not in menu and user said so
&& !PREF_MOUSE_GRAB_FORCE) {
return;
}
if (hasFocus && Tools.isAndroid8OrHigher()) mHostView.requestPointerCapture();
}
public void detach() {

View File

@ -132,7 +132,7 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
public void enable(boolean supposed) {
if(mDisplayState) return;
mDisplayState = true;
if(supposed && CallbackBridge.isGrabbing()) return;
if(supposed && CallbackBridge.isGrabbing() && LauncherPreferences.PREF_MOUSE_GRAB_FORCE) return;
_enable();
}

View File

@ -72,6 +72,8 @@ public class LauncherPreferences {
public static boolean PREF_FORCE_ENABLE_TOUCHCONTROLLER = false;
public static int PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = 100;
public static boolean PREF_MOUSE_GRAB_FORCE = false;
public static void loadPreferences(Context ctx) {
//Required for CTRLDEF_FILE and MultiRT
@ -114,6 +116,7 @@ public class LauncherPreferences {
PREF_VSYNC_IN_ZINK = DEFAULT_PREF.getBoolean("vsync_in_zink", true);
PREF_FORCE_ENABLE_TOUCHCONTROLLER = DEFAULT_PREF.getBoolean("forceEnableTouchController", false);
PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = DEFAULT_PREF.getInt("touchControllerVibrateLength", 100);
PREF_MOUSE_GRAB_FORCE = DEFAULT_PREF.getBoolean("always_grab_mouse", false);
String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) {

View File

@ -7,6 +7,7 @@ import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_INVERT_Y;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_SENSITIVITY;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_LONGPRESS_TRIGGER;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSESPEED;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SCALE_FACTOR;
import android.annotation.SuppressLint;
@ -31,11 +32,11 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
private SharedPreferences.Editor mEditor;
@SuppressLint("UseSwitchCompatOrMaterialCode")
private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch;
private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch;
private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar;
private TextView mGyroSensitivityText, mGyroSensitivityDisplayText, mMouseSpeedText, mGestureDelayText, mGestureDelayDisplayText, mResolutionText;
private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled;
private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled, mOriginalMouseGrab;
private float mOriginalGyroSensitivity, mOriginalMouseSpeed, mOriginalResolution;
private int mOriginalGestureDelay;
@ -65,6 +66,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mGyroXSwitch = mDialogContent.findViewById(R.id.checkboxGyroX);
mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY);
mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture);
mMouseGrabSwitch = mDialogContent.findViewById(R.id.always_grab_mouse_side_dialog);
mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar);
mMouseSpeedBar = mDialogContent.findViewById(R.id.editMouseSpeed_seekbar);
@ -86,6 +88,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mOriginalGyroXEnabled = PREF_GYRO_INVERT_X;
mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y;
mOriginalGestureDisabled = PREF_DISABLE_GESTURES;
mOriginalMouseGrab = PREF_MOUSE_GRAB_FORCE;
mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY;
mOriginalMouseSpeed = PREF_MOUSESPEED;
@ -96,6 +99,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mGyroXSwitch.setChecked(mOriginalGyroXEnabled);
mGyroYSwitch.setChecked(mOriginalGyroYEnabled);
mGestureSwitch.setChecked(mOriginalGestureDisabled);
mMouseGrabSwitch.setChecked(mOriginalMouseGrab);
mGyroSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
PREF_ENABLE_GYRO = isChecked;
@ -122,6 +126,11 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mEditor.putBoolean("disableGestures", isChecked);
});
mMouseGrabSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
PREF_MOUSE_GRAB_FORCE = isChecked;
mEditor.putBoolean("always_grab_mouse", isChecked);
});
mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
PREF_GYRO_SENSITIVITY = progress / 100f;
mEditor.putInt("gyroSensitivity", progress);
@ -156,6 +165,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
setSeekTextPercent(mResolutionText, mResolutionBar.getProgress());
updateMouseGrabVisibility();
updateGyroVisibility(mOriginalGyroEnabled);
updateGestureVisibility(mOriginalGestureDisabled);
}
@ -172,6 +182,10 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
target.setText(target.getContext().getString(format, value));
}
private void updateMouseGrabVisibility(){
mMouseGrabSwitch.setVisibility(Tools.isPointerDeviceConnected()? View.VISIBLE : View.GONE);
}
private void updateGyroVisibility(boolean isEnabled) {
int visibility = isEnabled ? View.VISIBLE : View.GONE;
mGyroXSwitch.setVisibility(visibility);
@ -202,6 +216,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mGyroXSwitch.setOnCheckedChangeListener(null);
mGyroYSwitch.setOnCheckedChangeListener(null);
mGestureSwitch.setOnCheckedChangeListener(null);
mMouseGrabSwitch.setOnCheckedChangeListener(null);
mGyroSensitivityBar.setOnSeekBarChangeListener(null);
mMouseSpeedBar.setOnSeekBarChangeListener(null);
@ -225,6 +240,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
PREF_GYRO_INVERT_X = mOriginalGyroXEnabled;
PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled;
PREF_DISABLE_GESTURES = mOriginalGestureDisabled;
PREF_MOUSE_GRAB_FORCE = mOriginalMouseGrab;
PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity;
PREF_MOUSESPEED = mOriginalMouseSpeed;

View File

@ -202,4 +202,15 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editGestureDelay_textView" />
<Switch
android:id="@+id/always_grab_mouse_side_dialog"
android:layout_width="match_parent"
android:layout_height="@dimen/_36sdp"
android:text="@string/mcl_setting_title_grab_mouse"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editGestureDelay_seekbar"
tools:ignore="UseSwitchCompatOrMaterialXml" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -171,6 +171,8 @@
<string name="mcl_disable_swap_hand_subtitle">Disables double tapping on the hotbar item to swap it in the second hand.</string>
<string name="mcl_setting_title_mousespeed">Mouse Speed</string>
<string name="mcl_setting_subtitle_mousespeed">Changes the speed of the virtual mouse.</string>
<string name="mcl_setting_title_grab_mouse">Use virtual cursor</string>
<string name="mcl_setting_subtitle_grab_mouse">Ensures the cursor stays inside the game. Prevents mouse from clicking touch control layout.</string>
<string name="customctrl_passthru">Mouse pass-thru</string>
<string name="customctrl_swipeable">Swipeable</string>
<string name="customctrl_forward_lock">Forward lock</string>

View File

@ -97,6 +97,11 @@
android:title="@string/preference_mouse_start_title"
android:summary="@string/preference_mouse_start_description"
/>
<SwitchPreference
android:key="always_grab_mouse"
android:title="@string/mcl_setting_title_grab_mouse"
android:summary="@string/mcl_setting_subtitle_grab_mouse"
android:defaultValue="false"/>
</PreferenceCategory>
<PreferenceCategory