fix(Pointer/Capture): Make non-opportunistic grabbing opt-in

Adds a new config option whether or not you want your mouse to be able
to click your touch controls! Default is you can!

Should also now let DeX users get their cursor out of the window by just
moving it by default.

This reverts commit 7631e3c9e70af03a294358bbfc7b809e21a1c690.
This commit is contained in:
alexytomi 2025-07-24 15:55:15 +08:00
parent 6e83225651
commit 6e6c20c450
7 changed files with 96 additions and 6 deletions

View File

@ -35,6 +35,7 @@ import android.provider.OpenableColumns;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.InputDevice;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.EditText; import android.widget.EditText;
@ -1557,4 +1558,19 @@ public final class Tools {
} }
return Integer.parseInt(iVersionArray[0] + iVersionArray[1] + iVersionArray[2]); 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,18 @@
package net.kdt.pojavlaunch.customcontrols.mouse; package net.kdt.pojavlaunch.customcontrols.mouse;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import net.kdt.pojavlaunch.GrabListener;
import net.kdt.pojavlaunch.MinecraftGLSurface; import net.kdt.pojavlaunch.MinecraftGLSurface;
import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.prefs.LauncherPreferences;
@ -15,7 +20,7 @@ import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge; import org.lwjgl.glfw.CallbackBridge;
@RequiresApi(api = Build.VERSION_CODES.O) @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 static final float TOUCHPAD_SCROLL_THRESHOLD = 1;
private final AbstractTouchpad mTouchpad; private final AbstractTouchpad mTouchpad;
private final View mHostView; private final View mHostView;
@ -32,14 +37,44 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
this.mHostView = hostView; this.mHostView = hostView;
hostView.setOnCapturedPointerListener(this); hostView.setOnCapturedPointerListener(this);
hostView.getViewTreeObserver().addOnWindowFocusChangeListener(this); hostView.getViewTreeObserver().addOnWindowFocusChangeListener(this);
if (!PREF_MOUSE_GRAB_FORCE)
CallbackBridge.addGrabListener(this);
} }
private void enableTouchpadIfNecessary() { private void enableTouchpadIfNecessary() {
if(!mTouchpad.getDisplayState()) mTouchpad.enable(true); if(!mTouchpad.getDisplayState() && !Tools.isPointerDeviceConnected()) 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() { public void handleAutomaticCapture() {
if(!mHostView.hasWindowFocus()) { if (!CallbackBridge.isGrabbing() // Only capture if not in menu and user said so
&& !PREF_MOUSE_GRAB_FORCE) {
return;
}
if (mHostView.hasPointerCapture()) {
enableTouchpadIfNecessary();
return;
}
if (!mHostView.hasWindowFocus()) {
mHostView.requestFocus(); mHostView.requestFocus();
} else { } else {
mHostView.requestPointerCapture(); mHostView.requestPointerCapture();
@ -128,7 +163,11 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
@Override @Override
public void onWindowFocusChanged(boolean hasFocus) { 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() { public void detach() {

View File

@ -72,6 +72,8 @@ public class LauncherPreferences {
public static boolean PREF_FORCE_ENABLE_TOUCHCONTROLLER = false; public static boolean PREF_FORCE_ENABLE_TOUCHCONTROLLER = false;
public static int PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = 100; public static int PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = 100;
public static boolean PREF_MOUSE_GRAB_FORCE = false;
public static void loadPreferences(Context ctx) { public static void loadPreferences(Context ctx) {
//Required for CTRLDEF_FILE and MultiRT //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_VSYNC_IN_ZINK = DEFAULT_PREF.getBoolean("vsync_in_zink", true);
PREF_FORCE_ENABLE_TOUCHCONTROLLER = DEFAULT_PREF.getBoolean("forceEnableTouchController", false); PREF_FORCE_ENABLE_TOUCHCONTROLLER = DEFAULT_PREF.getBoolean("forceEnableTouchController", false);
PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = DEFAULT_PREF.getInt("touchControllerVibrateLength", 100); 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="; String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) { 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_GYRO_SENSITIVITY;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_LONGPRESS_TRIGGER; 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_MOUSESPEED;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SCALE_FACTOR; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SCALE_FACTOR;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -31,11 +32,11 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
private SharedPreferences.Editor mEditor; private SharedPreferences.Editor mEditor;
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch; private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch;
private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar; private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar;
private TextView mGyroSensitivityText, mGyroSensitivityDisplayText, mMouseSpeedText, mGestureDelayText, mGestureDelayDisplayText, mResolutionText; 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 float mOriginalGyroSensitivity, mOriginalMouseSpeed, mOriginalResolution;
private int mOriginalGestureDelay; private int mOriginalGestureDelay;
@ -65,6 +66,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mGyroXSwitch = mDialogContent.findViewById(R.id.checkboxGyroX); mGyroXSwitch = mDialogContent.findViewById(R.id.checkboxGyroX);
mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY); mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY);
mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture); mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture);
mMouseGrabSwitch = mDialogContent.findViewById(R.id.always_grab_mouse_side_dialog);
mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar); mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar);
mMouseSpeedBar = mDialogContent.findViewById(R.id.editMouseSpeed_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; mOriginalGyroXEnabled = PREF_GYRO_INVERT_X;
mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y; mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y;
mOriginalGestureDisabled = PREF_DISABLE_GESTURES; mOriginalGestureDisabled = PREF_DISABLE_GESTURES;
mOriginalMouseGrab = PREF_MOUSE_GRAB_FORCE;
mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY; mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY;
mOriginalMouseSpeed = PREF_MOUSESPEED; mOriginalMouseSpeed = PREF_MOUSESPEED;
@ -122,6 +125,10 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mEditor.putBoolean("disableGestures", isChecked); mEditor.putBoolean("disableGestures", isChecked);
}); });
mMouseGrabSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
mEditor.putBoolean("always_grab_mouse", isChecked);
});
mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
PREF_GYRO_SENSITIVITY = progress / 100f; PREF_GYRO_SENSITIVITY = progress / 100f;
mEditor.putInt("gyroSensitivity", progress); mEditor.putInt("gyroSensitivity", progress);
@ -156,6 +163,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
setSeekTextPercent(mResolutionText, mResolutionBar.getProgress()); setSeekTextPercent(mResolutionText, mResolutionBar.getProgress());
updateMouseGrabVisibility();
updateGyroVisibility(mOriginalGyroEnabled); updateGyroVisibility(mOriginalGyroEnabled);
updateGestureVisibility(mOriginalGestureDisabled); updateGestureVisibility(mOriginalGestureDisabled);
} }
@ -172,6 +180,10 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
target.setText(target.getContext().getString(format, value)); target.setText(target.getContext().getString(format, value));
} }
private void updateMouseGrabVisibility(){
mMouseGrabSwitch.setVisibility(Tools.isPointerDeviceConnected()? View.VISIBLE : View.GONE);
}
private void updateGyroVisibility(boolean isEnabled) { private void updateGyroVisibility(boolean isEnabled) {
int visibility = isEnabled ? View.VISIBLE : View.GONE; int visibility = isEnabled ? View.VISIBLE : View.GONE;
mGyroXSwitch.setVisibility(visibility); mGyroXSwitch.setVisibility(visibility);
@ -202,6 +214,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
mGyroXSwitch.setOnCheckedChangeListener(null); mGyroXSwitch.setOnCheckedChangeListener(null);
mGyroYSwitch.setOnCheckedChangeListener(null); mGyroYSwitch.setOnCheckedChangeListener(null);
mGestureSwitch.setOnCheckedChangeListener(null); mGestureSwitch.setOnCheckedChangeListener(null);
mMouseGrabSwitch.setOnCheckedChangeListener(null);
mGyroSensitivityBar.setOnSeekBarChangeListener(null); mGyroSensitivityBar.setOnSeekBarChangeListener(null);
mMouseSpeedBar.setOnSeekBarChangeListener(null); mMouseSpeedBar.setOnSeekBarChangeListener(null);
@ -225,6 +238,7 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
PREF_GYRO_INVERT_X = mOriginalGyroXEnabled; PREF_GYRO_INVERT_X = mOriginalGyroXEnabled;
PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled; PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled;
PREF_DISABLE_GESTURES = mOriginalGestureDisabled; PREF_DISABLE_GESTURES = mOriginalGestureDisabled;
PREF_MOUSE_GRAB_FORCE = mOriginalMouseGrab;
PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity; PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity;
PREF_MOUSESPEED = mOriginalMouseSpeed; PREF_MOUSESPEED = mOriginalMouseSpeed;

View File

@ -202,4 +202,15 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editGestureDelay_textView" /> 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> </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_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_title_mousespeed">Mouse Speed</string>
<string name="mcl_setting_subtitle_mousespeed">Changes the speed of the virtual mouse.</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_passthru">Mouse pass-thru</string>
<string name="customctrl_swipeable">Swipeable</string> <string name="customctrl_swipeable">Swipeable</string>
<string name="customctrl_forward_lock">Forward lock</string> <string name="customctrl_forward_lock">Forward lock</string>

View File

@ -97,6 +97,11 @@
android:title="@string/preference_mouse_start_title" android:title="@string/preference_mouse_start_title"
android:summary="@string/preference_mouse_start_description" 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>
<PreferenceCategory <PreferenceCategory