From 06991751d640675d89906e10e022309bb66695f3 Mon Sep 17 00:00:00 2001 From: tomikun <60690056+alexytomi@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:05:21 +0800 Subject: [PATCH 1/4] feat: Keyboard panning when keyboard is used --- .../net/kdt/pojavlaunch/MainActivity.java | 17 ++++++++++++++++ .../customcontrols/mouse/Touchpad.java | 8 +++++--- .../prefs/LauncherPreferences.java | 2 ++ .../prefs/QuickSettingSideDialog.java | 20 +++++++++++++++++-- .../src/main/res/layout/activity_basemain.xml | 16 ++++++++++++++- .../main/res/layout/dialog_quick_setting.xml | 11 +++++++++- .../src/main/res/values/strings.xml | 2 ++ .../src/main/res/xml/pref_control.xml | 5 +++++ 8 files changed, 74 insertions(+), 7 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index f8cfe970f3..f0dc2de748 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -32,6 +32,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; @@ -110,6 +111,13 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (LauncherPreferences.PREF_KEYBOARD_PANNING) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + } else { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + } + if (LauncherPreferences.PREF_GAMEPAD_SDL_PASSTHRU) { // TODO: Use lower level HID capture that needs a dialogue box from the user for the // app to fully take focus of the input devices. Might cause issues with older android @@ -509,6 +517,15 @@ public void onResolutionChanged() { mHotbarView.onResolutionChanged(); } + @Override + public void onKeyboardPanningChanged() { + if (LauncherPreferences.PREF_KEYBOARD_PANNING) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + } else { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + } + } + @Override public void onGyroStateChanged() { mGyroControl.updateOrientation(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java index 78ca9e7109..f9d48fd2c4 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java @@ -123,9 +123,11 @@ public boolean getDisplayState() { @Override public void applyMotionVector(float x, float y) { - mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); - mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); - updateMousePosition(); + if (mDisplayState) { // Make sure no motion leaks through when disabling a moving cursor + mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); + mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); + updateMousePosition(); + } } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java index 543a23695e..057af91334 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java @@ -75,6 +75,7 @@ public class LauncherPreferences { public static int PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = 100; public static boolean PREF_MOUSE_GRAB_FORCE = false; + public static boolean PREF_KEYBOARD_PANNING = true; public static void loadPreferences(Context ctx) { @@ -121,6 +122,7 @@ public static void loadPreferences(Context ctx) { 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); + PREF_KEYBOARD_PANNING = DEFAULT_PREF.getBoolean("keyboardPanning", true); String argLwjglLibname = "-Dorg.lwjgl.opengl.libname="; for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java index 0689a7ce1d..e80011bf20 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java @@ -5,6 +5,7 @@ import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_INVERT_X; 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_KEYBOARD_PANNING; 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; @@ -32,11 +33,11 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView { private SharedPreferences.Editor mEditor; @SuppressLint("UseSwitchCompatOrMaterialCode") - private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch; + private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch, mKeyboardPanningSwitch; private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar; private TextView mGyroSensitivityText, mGyroSensitivityDisplayText, mMouseSpeedText, mGestureDelayText, mGestureDelayDisplayText, mResolutionText; - private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled, mOriginalMouseGrab; + private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled, mOriginalMouseGrab, mOriginalKeyboardPanning; private float mOriginalGyroSensitivity, mOriginalMouseSpeed, mOriginalResolution; private int mOriginalGestureDelay; @@ -67,6 +68,7 @@ private void bindLayout() { mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY); mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture); mMouseGrabSwitch = mDialogContent.findViewById(R.id.always_grab_mouse_side_dialog); + mKeyboardPanningSwitch = mDialogContent.findViewById(R.id.checkboxKeyboardPanning); mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar); mMouseSpeedBar = mDialogContent.findViewById(R.id.editMouseSpeed_seekbar); @@ -89,6 +91,7 @@ private void setupListeners() { mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y; mOriginalGestureDisabled = PREF_DISABLE_GESTURES; mOriginalMouseGrab = PREF_MOUSE_GRAB_FORCE; + mOriginalKeyboardPanning = PREF_KEYBOARD_PANNING; mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY; mOriginalMouseSpeed = PREF_MOUSESPEED; @@ -100,6 +103,7 @@ private void setupListeners() { mGyroYSwitch.setChecked(mOriginalGyroYEnabled); mGestureSwitch.setChecked(mOriginalGestureDisabled); mMouseGrabSwitch.setChecked(mOriginalMouseGrab); + mKeyboardPanningSwitch.setChecked(mOriginalKeyboardPanning); mGyroSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { PREF_ENABLE_GYRO = isChecked; @@ -131,6 +135,12 @@ private void setupListeners() { mEditor.putBoolean("always_grab_mouse", isChecked); }); + mKeyboardPanningSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + PREF_KEYBOARD_PANNING = isChecked; + onKeyboardPanningChanged(); + mEditor.putBoolean("keyboardPanning", isChecked); + }); + mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_GYRO_SENSITIVITY = progress / 100f; mEditor.putInt("gyroSensitivity", progress); @@ -217,6 +227,7 @@ private void removeListeners() { mGyroYSwitch.setOnCheckedChangeListener(null); mGestureSwitch.setOnCheckedChangeListener(null); mMouseGrabSwitch.setOnCheckedChangeListener(null); + mKeyboardPanningSwitch.setOnCheckedChangeListener(null); mGyroSensitivityBar.setOnSeekBarChangeListener(null); mMouseSpeedBar.setOnSeekBarChangeListener(null); @@ -241,6 +252,7 @@ public void cancel() { PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled; PREF_DISABLE_GESTURES = mOriginalGestureDisabled; PREF_MOUSE_GRAB_FORCE = mOriginalMouseGrab; + PREF_KEYBOARD_PANNING = mOriginalKeyboardPanning; PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity; PREF_MOUSESPEED = mOriginalMouseSpeed; @@ -249,6 +261,7 @@ public void cancel() { onGyroStateChanged(); onResolutionChanged(); + onKeyboardPanningChanged(); } disappear(true); @@ -257,6 +270,9 @@ public void cancel() { /** Called when the resolution is changed. Use {@link LauncherPreferences#PREF_SCALE_FACTOR} */ public abstract void onResolutionChanged(); + /** Called when the keyboard panning state is changed. Use {@link LauncherPreferences#PREF_KEYBOARD_PANNING} */ + public abstract void onKeyboardPanningChanged(); + /** Called when the gyro state is changed. * Use {@link LauncherPreferences#PREF_ENABLE_GYRO} * Use {@link LauncherPreferences#PREF_GYRO_INVERT_X} diff --git a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml index ac34158ccc..b258b96200 100644 --- a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml +++ b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml @@ -38,11 +38,24 @@ android:translationZ="1dp" android:visibility="gone"/> + + + android:layout_height="1dp" + android:layout_gravity="bottom" /> + + \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 6f695e916d..ce70ec7288 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -176,6 +176,8 @@ Changes the speed of the virtual mouse. Use virtual cursor Ensures the cursor stays inside the game. Prevents mouse from clicking touch control layout. + Keyboard panning + Shift the screen upwards when the keyboard is open. Mouse pass-thru Swipeable Forward lock diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml index 7747d0c7a4..5dc60d68a8 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_control.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml @@ -62,6 +62,11 @@ android:title="@string/mcl_setting_title_buttonallcaps" android:summary="@string/mcl_setting_subtitle_buttonallcaps" /> + From 07cc39eac531f954b06fc838c3e2bd63d34dc214 Mon Sep 17 00:00:00 2001 From: fifth_light Date: Tue, 9 Jun 2026 16:11:46 +0800 Subject: [PATCH 2/4] feat(TouchController): add IME padding support --- .../src/main/AndroidManifest.xml | 1 + .../net/kdt/pojavlaunch/MainActivity.java | 123 ++++++++++++++- .../utils/TouchControllerInputView.java | 145 +++++++++++------- .../utils/TouchControllerUtils.java | 2 +- 4 files changed, 203 insertions(+), 68 deletions(-) diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml index a25fb0d92e..9af22d294f 100644 --- a/app_pojavlauncher/src/main/AndroidManifest.xml +++ b/app_pojavlauncher/src/main/AndroidManifest.xml @@ -110,6 +110,7 @@ android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|keyboard|navigation|uiMode" android:launchMode="singleTop" android:process=":game" + android:windowSoftInputMode="adjustResize" android:screenOrientation="sensorLandscape" /> runningAnimations) { + // Find an IME animation + for (WindowInsetsAnimationCompat animation : runningAnimations) { + if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { + imeAnimation = animation; + break; + } + } + refreshImeTranslation(); + return insets; + } + + @Override + public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { + imeAnimation = null; + refreshImeTranslation(); + } + }); + ViewCompat.setOnApplyWindowInsetsListener(contentFrame, (v, insets) -> { + boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); + if (imeVisible) { + int height = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; + fullImeHeight = height; + targetImeHeight = height; + } else { + // Retain last value of fullImeHeight + targetImeHeight = 0; + } + return insets; + }); + // Set the activity for the executor. Must do this here, or else Tools.showErrorRemote() may not // execute the correct method ContextExecutor.setActivity(this); @@ -217,7 +279,7 @@ protected void initLayout(int resId) { GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); touchCharInput.setCharacterSender(new LwjglCharSender()); - touchControllerInputView.setSize(minecraftGLView.getWidth(), minecraftGLView.getHeight()); + touchControllerInputView.setInputAreaRectListener(this); if(minecraftProfile.pojavRendererName != null) { Log.i("RdrDebug","__P_renderer="+minecraftProfile.pojavRendererName); @@ -268,6 +330,9 @@ protected void initLayout(int resId) { touchpad.post(() -> touchpad.switchState()); } + // At this time, correct size is known + touchControllerInputView.setSize(minecraftGLView.getWidth(), minecraftGLView.getHeight()); + runCraft(finalVersion, mVersionInfo); }catch (Throwable e){ Tools.showErrorRemote(e); @@ -322,6 +387,7 @@ private void bindValues(){ touchControllerInputView = findViewById(R.id.touch_controller_input); mDrawerPullButton = findViewById(R.id.drawer_button); mHotbarView = findViewById(R.id.hotbar_view); + contentFrame = findViewById(R.id.content_frame); } @Override @@ -718,4 +784,45 @@ public void onTrimMemory(int level) { public void onBackPressed() { super.onBackPressed(); } + + @Override + public void updateInputAreaRect(@Nullable RectF rect) { + inputAreaRect = rect; + refreshImeTranslation(); + } + + private void refreshImeTranslation() { + if (fullImeHeight == 0) { + // Early exit + contentFrame.setTranslationY(0); + return; + } + + int inputAreaBottom; + if (inputAreaRect != null) { + inputAreaBottom = (int) inputAreaRect.bottom; + } else if (LauncherPreferences.PREF_KEYBOARD_PANNING) { + inputAreaBottom = contentFrame.getHeight(); + } else { + contentFrame.setTranslationY(0); + return; + } + + int animationImeHeight; + if (imeAnimation != null) { + if (targetImeHeight == 0) { + // Collapsing + animationImeHeight = (int) (fullImeHeight * (1 - imeAnimation.getInterpolatedFraction())); + } else { + // Expanding + animationImeHeight = (int) (targetImeHeight * imeAnimation.getInterpolatedFraction()); + } + } else { + animationImeHeight = targetImeHeight; + } + int bottomDistance = contentFrame.getHeight() - inputAreaBottom; + int bottomPadding = Math.max(animationImeHeight - bottomDistance, 0); + + contentFrame.setTranslationY(-bottomPadding); + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java index ab47473089..dd14e2481d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java @@ -6,6 +6,7 @@ import android.graphics.RectF; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; @@ -39,8 +40,8 @@ import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode; import net.kdt.pojavlaunch.customcontrols.keyboard.CharacterSenderStrategy; -public class TouchControllerInputView extends View { - public boolean disableFullScreenInput = false; +public class TouchControllerInputView extends View implements LauncherProxyClient.InputHandler, LauncherProxyClient.KeyboardShowHandler { + public boolean disableFullScreenInput = true; private LauncherProxyClient client; private int width = -1; private int height = -1; @@ -52,7 +53,11 @@ public class TouchControllerInputView extends View { private FloatRect cursorRect; private FloatRect inputAreaRect; private InputConnectionImpl inputConnection; - private final LauncherProxyClient.InputHandler inputHandler; + + public interface InputAreaRectListener { + void updateInputAreaRect(@Nullable RectF rect); + } + private InputAreaRectListener inputAreaRectListener; public TouchControllerInputView(Context context) { this(context, null); @@ -68,57 +73,68 @@ public TouchControllerInputView(Context context, @Nullable AttributeSet attrs, i if (inputMethodManager == null) { throw new IllegalStateException("No InputMethodManager service"); } + } - inputHandler = new LauncherProxyClient.InputHandler() { - @Override - public void updateState(TextInputState textInputState) { - post(() -> { - TextInputState prevState = inputState; - inputState = textInputState; - if (textInputState != null) { - setVisibility(VISIBLE); - setFocusable(true); - } - if (prevState == null && textInputState != null) { - setFocusableInTouchMode(true); - clearFocus(); - requestFocus(); - inputMethodManager.showSoftInput( - TouchControllerInputView.this, - InputMethodManager.SHOW_IMPLICIT - ); - } else if (prevState != null && textInputState == null) { - clearFocus(); - inputMethodManager.hideSoftInputFromWindow( - getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY - ); - } - if (textInputState != null) { - if (inputConnection != null) { - inputConnection.updateState(textInputState); - } - } else { - setVisibility(GONE); - setFocusable(false); - } - }); - } - @Override - public void updateCursor(FloatRect cursorRect) { - TouchControllerInputView.this.cursorRect = cursorRect; - updateCursorAnchorInfo(); + @Override + public void updateState(TextInputState textInputState) { + post(() -> { + TextInputState prevState = inputState; + inputState = textInputState; + if (textInputState != null) { + setVisibility(VISIBLE); + setFocusable(true); + } + if (prevState == null && textInputState != null) { + setFocusableInTouchMode(true); + clearFocus(); + requestFocus(); + inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); + } else if (prevState != null && textInputState == null) { + clearFocus(); + inputMethodManager.hideSoftInputFromWindow( + getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY + ); } + if (textInputState != null) { + if (inputConnection != null) { + inputConnection.updateState(textInputState); + } + } else { + setVisibility(GONE); + setFocusable(false); + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(null); + } + } + }); + } - @Override - public void updateArea(FloatRect inputAreaRect) { - TouchControllerInputView.this.inputAreaRect = inputAreaRect; - updateCursorAnchorInfo(); + @Override + public void updateCursor(FloatRect cursorRect) { + TouchControllerInputView.this.cursorRect = cursorRect; + updateCursorAnchorInfo(); + } + + @Override + public void updateArea(FloatRect inputAreaRect) { + TouchControllerInputView.this.inputAreaRect = inputAreaRect; + updateCursorAnchorInfo(); + } + + @Override + public void showKeyboard() { + post(() -> { + if (inputState != null) { + inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); } - }; + }); } + @Override + public void hideKeyboard() {} + private static boolean isEmpty(TextRange range) { return range.getLength() == 0; } @@ -143,10 +159,12 @@ public void setClient(LauncherProxyClient value) { LauncherProxyClient prev = this.client; if (prev != null) { prev.setInputHandler(null); + prev.setKeyboardShowHandler(null); } this.client = value; if (value != null) { - value.setInputHandler(inputHandler); + value.setInputHandler(this); + value.setKeyboardShowHandler(this); } } @@ -155,6 +173,10 @@ public void setSize(int width, int height) { this.height = height; } + public void setInputAreaRectListener(InputAreaRectListener listener) { + inputAreaRectListener = listener; + } + @Override public boolean onCheckIsTextEditor() { return true; @@ -198,16 +220,18 @@ private void updateCursorAnchorInfo() { ); } if (inputAreaRect != null) { + RectF rect = new RectF( + inputAreaRect.getLeft() * width, + inputAreaRect.getTop() * height, + (inputAreaRect.getLeft() + inputAreaRect.getWidth()) * width, + (inputAreaRect.getTop() + inputAreaRect.getHeight()) * height + ); + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(rect); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { EditorBoundsInfo editorBoundsInfo = new EditorBoundsInfo.Builder() - .setEditorBounds( - new RectF( - inputAreaRect.getLeft() * width, - inputAreaRect.getTop() * height, - (inputAreaRect.getLeft() + inputAreaRect.getWidth()) * width, - (inputAreaRect.getTop() + inputAreaRect.getHeight()) * height - ) - ) + .setEditorBounds(rect) .build(); builder.setEditorBoundsInfo(editorBoundsInfo); } @@ -307,6 +331,9 @@ public boolean clearMetaKeyStates(int states) { @Override public void closeConnection() { + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(null); + } } @Override @@ -489,7 +516,7 @@ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { @Nullable @Override - public android.os.Handler getHandler() { + public Handler getHandler() { return null; } @@ -562,12 +589,12 @@ public boolean performContextMenuAction(int id) { @Override public boolean performEditorAction(int editorAction) { - return false; + return true; } @Override public boolean performPrivateCommand(String action, Bundle data) { - return false; + return true; } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java index fa3a3b60a4..9335f5624f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java @@ -106,7 +106,7 @@ public static void initialize(Context context, TouchControllerInputView touchCon Log.w("TouchController", "Failed to set TouchController environment variable", e); } MessageTransport transport = UnixSocketTransportKt.UnixSocketTransport(socketName); - proxyClient = new LauncherProxyClient(transport, Set.of(PlatformCapability.TEXT_STATUS)); + proxyClient = new LauncherProxyClient(transport, Set.of(PlatformCapability.TEXT_STATUS, PlatformCapability.KEYBOARD_SHOW)); proxyClient.run(); touchControllerInputView.setClient(proxyClient); Vibrator vibrator = ContextCompat.getSystemService(context, Vibrator.class); From 86b27b056ac931358d2a3f0e8bb86f4a800238f6 Mon Sep 17 00:00:00 2001 From: fifth_light Date: Tue, 9 Jun 2026 23:12:23 +0800 Subject: [PATCH 3/4] fix(TouchController): use actual insets value for IME animation --- .../net/kdt/pojavlaunch/MainActivity.java | 60 +++++-------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index 0902ae37ae..540e5fa71a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -109,10 +109,8 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe @Nullable private RectF inputAreaRect; - @Nullable - private WindowInsetsAnimationCompat imeAnimation; - private int fullImeHeight; - private int targetImeHeight; + private int imeHeight; + private boolean hasOngoingImeAnimation; MinecraftProfile minecraftProfile; @@ -204,53 +202,35 @@ public void onCreate(Bundle savedInstanceState) { MCOptionUtils.addMCOptionListener(optionListener); mControlLayout.setModifiable(false); - // Listen to IME offsets + // Listen to IME insets animation ViewCompat.setWindowInsetsAnimationCallback(contentFrame, new WindowInsetsAnimationCompat.Callback(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP) { @Override public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) { if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { - imeAnimation = animation; + hasOngoingImeAnimation = true; } } - @NonNull - @Override - public WindowInsetsAnimationCompat.BoundsCompat onStart(@NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds) { - if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { - refreshImeTranslation(); - } - return bounds; - } - @NonNull @Override public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List runningAnimations) { - // Find an IME animation - for (WindowInsetsAnimationCompat animation : runningAnimations) { - if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { - imeAnimation = animation; - break; - } - } + imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; refreshImeTranslation(); return insets; } @Override public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { - imeAnimation = null; - refreshImeTranslation(); + if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { + hasOngoingImeAnimation = false; + } } }); ViewCompat.setOnApplyWindowInsetsListener(contentFrame, (v, insets) -> { - boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (imeVisible) { - int height = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; - fullImeHeight = height; - targetImeHeight = height; - } else { - // Retain last value of fullImeHeight - targetImeHeight = 0; + // Only refresh translation if IME change insets itself, not the animation + if (!hasOngoingImeAnimation) { + imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; + refreshImeTranslation(); } return insets; }); @@ -792,7 +772,7 @@ public void updateInputAreaRect(@Nullable RectF rect) { } private void refreshImeTranslation() { - if (fullImeHeight == 0) { + if (imeHeight == 0) { // Early exit contentFrame.setTranslationY(0); return; @@ -808,20 +788,8 @@ private void refreshImeTranslation() { return; } - int animationImeHeight; - if (imeAnimation != null) { - if (targetImeHeight == 0) { - // Collapsing - animationImeHeight = (int) (fullImeHeight * (1 - imeAnimation.getInterpolatedFraction())); - } else { - // Expanding - animationImeHeight = (int) (targetImeHeight * imeAnimation.getInterpolatedFraction()); - } - } else { - animationImeHeight = targetImeHeight; - } int bottomDistance = contentFrame.getHeight() - inputAreaBottom; - int bottomPadding = Math.max(animationImeHeight - bottomDistance, 0); + int bottomPadding = Math.max(imeHeight - bottomDistance, 0); contentFrame.setTranslationY(-bottomPadding); } From 35ac3b05a9076f5216b7eab80987bdccfac9d358 Mon Sep 17 00:00:00 2001 From: fifth_light Date: Wed, 10 Jun 2026 01:22:19 +0800 Subject: [PATCH 4/4] fix(TouchController): update cursor and input area in UI thread --- .../pojavlaunch/utils/TouchControllerInputView.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java index dd14e2481d..41148ed8d6 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java @@ -113,14 +113,18 @@ public void updateState(TextInputState textInputState) { @Override public void updateCursor(FloatRect cursorRect) { - TouchControllerInputView.this.cursorRect = cursorRect; - updateCursorAnchorInfo(); + post(() -> { + TouchControllerInputView.this.cursorRect = cursorRect; + updateCursorAnchorInfo(); + }); } @Override public void updateArea(FloatRect inputAreaRect) { - TouchControllerInputView.this.inputAreaRect = inputAreaRect; - updateCursorAnchorInfo(); + post(() -> { + TouchControllerInputView.this.inputAreaRect = inputAreaRect; + updateCursorAnchorInfo(); + }); } @Override