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