From 64b214bca03322aa5fe3c3a1e871eb1a8e771d92 Mon Sep 17 00:00:00 2001 From: BunnyTaylor Date: Sun, 3 May 2026 03:02:13 -0400 Subject: [PATCH 1/2] Add option to rotate external trackpad axes 90 degrees Some keyboard trackpads (e.g. Samsung Galaxy Tab S11 Ultra Pro Keyboard) report AXIS_RELATIVE_X/Y rotated 90 degrees clockwise, causing up/down movement to move the cursor left/right and vice versa. This adds a user toggle in Input Settings to correct the axes. --- app/src/main/java/com/limelight/Game.java | 17 +++++++++++++++-- .../preferences/PreferenceConfiguration.java | 4 ++++ app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/preferences.xml | 5 +++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 5d214508a3..5de353f68d 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -1827,8 +1827,21 @@ else if (event.getAction() == MotionEvent.ACTION_UP) { // significantly different than before. if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) { // Send the deltas straight from the motion event - short deltaX = (short)inputCaptureProvider.getRelativeAxisX(event); - short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event); + float rawX = inputCaptureProvider.getRelativeAxisX(event); + float rawY = inputCaptureProvider.getRelativeAxisY(event); + + // Some keyboard trackpads (e.g. Samsung Galaxy Tab S11 Ultra Pro Keyboard) + // report AXIS_RELATIVE_X/Y rotated 90° clockwise: the driver sends physical-Y + // as AXIS_RELATIVE_X and physical-X as AXIS_RELATIVE_Y. Correcting means + // reading Y as X and negating the new Y. + if (prefConfig.rotateTouchpadAxes && eventSource == InputDevice.SOURCE_TOUCHPAD) { + float tmp = rawX; + rawX = rawY; + rawY = -tmp; + } + + short deltaX = (short) rawX; + short deltaY = (short) rawY; if (deltaX != 0 || deltaY != 0) { if (prefConfig.absoluteMouseMode) { diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index 8ed01e3610..f185018e36 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -68,6 +68,7 @@ public enum AnalogStickForScrolling { private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse"; private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors"; private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback"; + private static final String ROTATE_TOUCHPAD_AXES_PREF_STRING = "checkbox_rotate_touchpad_axes"; static final String DEFAULT_RESOLUTION = "1280x720"; static final String DEFAULT_FPS = "60"; @@ -108,6 +109,7 @@ public enum AnalogStickForScrolling { private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false; private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true; private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false; + private static final boolean DEFAULT_ROTATE_TOUCHPAD_AXES = false; public static final int FRAME_PACING_MIN_LATENCY = 0; public static final int FRAME_PACING_BALANCED = 1; @@ -155,6 +157,7 @@ public enum AnalogStickForScrolling { public boolean gamepadMotionSensors; public boolean gamepadTouchpadAsMouse; public boolean gamepadMotionSensorsFallbackToDevice; + public boolean rotateTouchpadAxes; public static boolean isNativeResolution(int width, int height) { // It's not a native resolution if it matches an existing resolution option @@ -601,6 +604,7 @@ else if (audioConfig.equals("51")) { config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE); config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS); config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK); + config.rotateTouchpadAxes = prefs.getBoolean(ROTATE_TOUCHPAD_AXES_PREF_STRING, DEFAULT_ROTATE_TOUCHPAD_AXES); return config; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cc2215aaa..aed475349e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -199,6 +199,8 @@ If enabled, the touchscreen acts like a trackpad. If disabled, the touchscreen directly controls the mouse cursor. Remote desktop mouse mode This can make mouse acceleration behave more naturally for remote desktop usage, but it is incompatible with many games. + Rotate external trackpad axes + Enable if your keyboard trackpad moves the cursor in the wrong direction (e.g. up/down movement causes left/right movement). Fixes devices like the Samsung Galaxy Tab Pro Keyboard. Enable back and forward mouse buttons Enabling this option may break right clicking on some buggy devices diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 5b09082b35..d9de08c279 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -144,6 +144,11 @@ android:title="@string/title_checkbox_absolute_mouse_mode" android:summary="@string/summary_checkbox_absolute_mouse_mode" android:defaultValue="false" /> + From 4502549b9f617ef9e41c91d93a917e821edb6f3e Mon Sep 17 00:00:00 2001 From: BunnyTaylor Date: Sun, 3 May 2026 03:52:21 -0400 Subject: [PATCH 2/2] Add two-finger trackpad scroll and invert scroll direction option Samsung Galaxy Tab keyboard trackpads send two-finger movement as ACTION_MOVE events with getPointerCount() == 2 rather than ACTION_SCROLL with AXIS_VSCROLL/HSCROLL. Detect this and route it to scroll instead of cursor movement. Also adds an "Invert scroll direction" toggle in Input Settings that reverses scroll direction for both trackpad two-finger scroll and standard ACTION_SCROLL events (e.g. mouse wheel). --- app/src/main/java/com/limelight/Game.java | 37 ++++++++++++------- .../preferences/PreferenceConfiguration.java | 4 ++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 5 +++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 5de353f68d..a98b0e9ffb 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -1826,7 +1826,6 @@ else if (event.getAction() == MotionEvent.ACTION_UP) { // dealing with a stylus without hover support, our position might be // significantly different than before. if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) { - // Send the deltas straight from the motion event float rawX = inputCaptureProvider.getRelativeAxisX(event); float rawY = inputCaptureProvider.getRelativeAxisY(event); @@ -1840,17 +1839,28 @@ else if (event.getAction() == MotionEvent.ACTION_UP) { rawY = -tmp; } - short deltaX = (short) rawX; - short deltaY = (short) rawY; - - if (deltaX != 0 || deltaY != 0) { - if (prefConfig.absoluteMouseMode) { - // NB: view may be null, but we can unconditionally use streamView because we don't need to adjust - // relative axis deltas for the position of the streamView within the parent's coordinate system. - conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)streamView.getWidth(), (short)streamView.getHeight()); + // Samsung keyboard trackpads send two-finger scroll as ACTION_MOVE with + // getPointerCount() == 2, using the same REL_X/REL_Y axes as cursor movement + // rather than ACTION_SCROLL with AXIS_VSCROLL/HSCROLL. + if (eventSource == InputDevice.SOURCE_TOUCHPAD && event.getPointerCount() == 2) { + if (rawX != 0 || rawY != 0) { + int scrollDir = prefConfig.naturalScroll ? 1 : -1; + conn.sendMouseHighResScroll((short)(rawY * 10 * scrollDir)); + conn.sendMouseHighResHScroll((short)(-rawX * 10 * scrollDir)); } - else { - conn.sendMouseMove(deltaX, deltaY); + } else { + short deltaX = (short) rawX; + short deltaY = (short) rawY; + + if (deltaX != 0 || deltaY != 0) { + if (prefConfig.absoluteMouseMode) { + // NB: view may be null, but we can unconditionally use streamView because we don't need to adjust + // relative axis deltas for the position of the streamView within the parent's coordinate system. + conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)streamView.getWidth(), (short)streamView.getHeight()); + } + else { + conn.sendMouseMove(deltaX, deltaY); + } } } } @@ -1891,8 +1901,9 @@ else if (view != null) { if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { // Send the vertical scroll packet - conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120)); - conn.sendMouseHighResHScroll((short)(event.getAxisValue(MotionEvent.AXIS_HSCROLL) * 120)); + int scrollDir = prefConfig.naturalScroll ? -1 : 1; + conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120 * scrollDir)); + conn.sendMouseHighResHScroll((short)(event.getAxisValue(MotionEvent.AXIS_HSCROLL) * 120 * scrollDir)); } if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) { diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index f185018e36..920e62cbac 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -69,6 +69,7 @@ public enum AnalogStickForScrolling { private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors"; private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback"; private static final String ROTATE_TOUCHPAD_AXES_PREF_STRING = "checkbox_rotate_touchpad_axes"; + private static final String NATURAL_SCROLL_PREF_STRING = "checkbox_natural_scroll"; static final String DEFAULT_RESOLUTION = "1280x720"; static final String DEFAULT_FPS = "60"; @@ -110,6 +111,7 @@ public enum AnalogStickForScrolling { private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true; private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false; private static final boolean DEFAULT_ROTATE_TOUCHPAD_AXES = false; + private static final boolean DEFAULT_NATURAL_SCROLL = false; public static final int FRAME_PACING_MIN_LATENCY = 0; public static final int FRAME_PACING_BALANCED = 1; @@ -158,6 +160,7 @@ public enum AnalogStickForScrolling { public boolean gamepadTouchpadAsMouse; public boolean gamepadMotionSensorsFallbackToDevice; public boolean rotateTouchpadAxes; + public boolean naturalScroll; public static boolean isNativeResolution(int width, int height) { // It's not a native resolution if it matches an existing resolution option @@ -605,6 +608,7 @@ else if (audioConfig.equals("51")) { config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS); config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK); config.rotateTouchpadAxes = prefs.getBoolean(ROTATE_TOUCHPAD_AXES_PREF_STRING, DEFAULT_ROTATE_TOUCHPAD_AXES); + config.naturalScroll = prefs.getBoolean(NATURAL_SCROLL_PREF_STRING, DEFAULT_NATURAL_SCROLL); return config; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aed475349e..c0f4ed4d1f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -201,6 +201,8 @@ This can make mouse acceleration behave more naturally for remote desktop usage, but it is incompatible with many games. Rotate external trackpad axes Enable if your keyboard trackpad moves the cursor in the wrong direction (e.g. up/down movement causes left/right movement). Fixes devices like the Samsung Galaxy Tab Pro Keyboard. + Invert scroll direction + Reverses the scroll direction for trackpad and mouse scroll wheel. Enable back and forward mouse buttons Enabling this option may break right clicking on some buggy devices diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d9de08c279..3bf0ab9cbf 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -149,6 +149,11 @@ android:title="@string/title_checkbox_rotate_touchpad_axes" android:summary="@string/summary_checkbox_rotate_touchpad_axes" android:defaultValue="false" /> +